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    /// Container image URI for `PackageType=Image` functions. Points at a
27    /// private or public ECR image that the runtime pulls at invoke time.
28    /// `None` for `PackageType=Zip`.
29    #[serde(default)]
30    pub image_uri: Option<String>,
31    /// Resource-based policy attached to this function via
32    /// `AddPermission`, serialized as a full JSON policy document
33    /// (`{"Version":"2012-10-17","Statement":[...]}`). `None` means
34    /// the function has no resource policy attached, matching the
35    /// `ResourceNotFoundException` AWS returns from `GetPolicy` in
36    /// that state. `AddPermission` lazily initializes this; every
37    /// `RemovePermission` leaves at least `{"Statement":[]}` behind,
38    /// matching AWS behavior.
39    pub policy: Option<String>,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct EventSourceMapping {
44    pub uuid: String,
45    pub function_arn: String,
46    pub event_source_arn: String,
47    pub batch_size: i64,
48    pub enabled: bool,
49    pub state: String,
50    pub last_modified: DateTime<Utc>,
51    /// Raw `Filters: [{Pattern: "..."}]` array as supplied via
52    /// `FilterCriteria`. Each pattern is an EventBridge-style JSON
53    /// pattern matched against the record body — non-matching records
54    /// are dropped.
55    #[serde(default)]
56    pub filter_patterns: Vec<String>,
57    /// Wait up to N seconds to accumulate `batch_size` records before
58    /// invoking. Implemented as a deadline check inside the poller.
59    #[serde(default)]
60    pub maximum_batching_window_in_seconds: Option<i64>,
61    /// `LATEST`, `TRIM_HORIZON`, or `AT_TIMESTAMP`. Honored on the
62    /// first poll for stream sources (Kinesis, DDB Streams).
63    #[serde(default)]
64    pub starting_position: Option<String>,
65    /// Optional epoch-second timestamp paired with
66    /// `StartingPosition=AT_TIMESTAMP`.
67    #[serde(default)]
68    pub starting_position_timestamp: Option<f64>,
69    /// Kinesis-only: number of concurrent batch invocations per shard.
70    #[serde(default)]
71    pub parallelization_factor: Option<i64>,
72    /// `["ReportBatchItemFailures"]` to opt into partial-batch failure
73    /// semantics. Empty / unset = entire batch is retried on error.
74    #[serde(default)]
75    pub function_response_types: Vec<String>,
76}
77
78/// A recorded Lambda invocation from cross-service delivery.
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct LambdaInvocation {
81    pub function_arn: String,
82    pub payload: String,
83    pub timestamp: DateTime<Utc>,
84    pub source: String,
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct LambdaState {
89    pub account_id: String,
90    pub region: String,
91    #[serde(default)]
92    pub functions: HashMap<String, LambdaFunction>,
93    #[serde(default)]
94    pub event_source_mappings: HashMap<String, EventSourceMapping>,
95    /// Recorded invocations from cross-service integrations — not persisted.
96    #[serde(default, skip)]
97    pub invocations: Vec<LambdaInvocation>,
98    /// Per-function aliases keyed by `{function}:{alias}`.
99    #[serde(default)]
100    pub aliases: HashMap<String, FunctionAlias>,
101    /// Published versions per function (function_name -> Vec<version>).
102    #[serde(default)]
103    pub function_versions: HashMap<String, Vec<String>>,
104    /// Layers keyed by name.
105    #[serde(default)]
106    pub layers: HashMap<String, Layer>,
107    /// Function URL configs keyed by function name.
108    #[serde(default)]
109    pub function_url_configs: HashMap<String, FunctionUrlConfig>,
110    /// Reserved concurrency configs keyed by function name.
111    #[serde(default)]
112    pub function_concurrency: HashMap<String, i64>,
113    /// Provisioned concurrency configs keyed by `{function}:{qualifier}`.
114    #[serde(default)]
115    pub provisioned_concurrency: HashMap<String, ProvisionedConcurrencyConfig>,
116    /// Code signing configs keyed by id.
117    #[serde(default)]
118    pub code_signing_configs: HashMap<String, CodeSigningConfig>,
119    /// Function-to-code-signing-config association keyed by function name.
120    #[serde(default)]
121    pub function_code_signing: HashMap<String, String>,
122    /// Event invoke configs keyed by `{function}:{qualifier}`.
123    #[serde(default)]
124    pub event_invoke_configs: HashMap<String, EventInvokeConfig>,
125    /// Runtime management configs keyed by `{function}:{qualifier}`.
126    #[serde(default)]
127    pub runtime_management: HashMap<String, RuntimeManagementConfig>,
128    /// Scaling configs keyed by event source mapping uuid.
129    #[serde(default)]
130    pub scaling_configs: HashMap<String, FunctionScalingConfig>,
131    /// Recursion configs keyed by function name.
132    #[serde(default)]
133    pub recursion_configs: HashMap<String, String>,
134    /// Tags keyed by resource ARN.
135    #[serde(default)]
136    pub tags: HashMap<String, Vec<(String, String)>>,
137    /// Capacity providers keyed by name.
138    #[serde(default)]
139    pub capacity_providers: HashMap<String, CapacityProvider>,
140    /// Durable executions keyed by id.
141    #[serde(default)]
142    pub durable_executions: HashMap<String, DurableExecution>,
143    /// Account settings (single per-account record).
144    #[serde(default)]
145    pub account_settings: Option<AccountSettings>,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct FunctionAlias {
150    pub alias_arn: String,
151    pub name: String,
152    pub function_version: String,
153    pub description: String,
154    pub revision_id: String,
155    pub routing_config: Option<serde_json::Value>,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct Layer {
160    pub layer_name: String,
161    pub layer_arn: String,
162    pub versions: Vec<LayerVersion>,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct LayerVersion {
167    pub version: i64,
168    pub layer_version_arn: String,
169    pub description: String,
170    pub created_date: DateTime<Utc>,
171    pub compatible_runtimes: Vec<String>,
172    pub license_info: String,
173    pub policy: Option<String>,
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct FunctionUrlConfig {
178    pub function_arn: String,
179    pub function_url: String,
180    pub auth_type: String,
181    pub cors: Option<serde_json::Value>,
182    pub creation_time: DateTime<Utc>,
183    pub last_modified_time: DateTime<Utc>,
184    pub invoke_mode: String,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct ProvisionedConcurrencyConfig {
189    pub requested: i64,
190    pub allocated: i64,
191    pub status: String,
192    pub last_modified: DateTime<Utc>,
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct CodeSigningConfig {
197    pub csc_id: String,
198    pub csc_arn: String,
199    pub description: String,
200    pub allowed_publishers: Vec<String>,
201    pub untrusted_artifact_action: String,
202    pub last_modified: DateTime<Utc>,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize)]
206pub struct EventInvokeConfig {
207    pub function_arn: String,
208    pub maximum_event_age: i64,
209    pub maximum_retry_attempts: i64,
210    pub destination_config: serde_json::Value,
211    pub last_modified: DateTime<Utc>,
212}
213
214#[derive(Debug, Clone, Serialize, Deserialize)]
215pub struct RuntimeManagementConfig {
216    pub update_runtime_on: String,
217    pub runtime_version_arn: String,
218}
219
220#[derive(Debug, Clone, Serialize, Deserialize)]
221pub struct FunctionScalingConfig {
222    pub maximum_concurrency: i64,
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize)]
226pub struct CapacityProvider {
227    pub name: String,
228    pub arn: String,
229    pub status: String,
230    pub created: DateTime<Utc>,
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct DurableExecution {
235    pub id: String,
236    pub function_arn: String,
237    pub status: String,
238    pub started: DateTime<Utc>,
239    pub stopped: Option<DateTime<Utc>>,
240    pub history: Vec<serde_json::Value>,
241    pub state: serde_json::Value,
242}
243
244#[derive(Debug, Clone, Serialize, Deserialize, Default)]
245pub struct AccountSettings {
246    pub concurrent_executions: i64,
247    pub code_size_zipped: i64,
248    pub code_size_unzipped: i64,
249    pub total_code_size: i64,
250}
251
252impl LambdaState {
253    pub fn new(account_id: &str, region: &str) -> Self {
254        Self {
255            account_id: account_id.to_string(),
256            region: region.to_string(),
257            functions: HashMap::new(),
258            event_source_mappings: HashMap::new(),
259            invocations: Vec::new(),
260            aliases: HashMap::new(),
261            function_versions: HashMap::new(),
262            layers: HashMap::new(),
263            function_url_configs: HashMap::new(),
264            function_concurrency: HashMap::new(),
265            provisioned_concurrency: HashMap::new(),
266            code_signing_configs: HashMap::new(),
267            function_code_signing: HashMap::new(),
268            event_invoke_configs: HashMap::new(),
269            runtime_management: HashMap::new(),
270            scaling_configs: HashMap::new(),
271            recursion_configs: HashMap::new(),
272            tags: HashMap::new(),
273            capacity_providers: HashMap::new(),
274            durable_executions: HashMap::new(),
275            account_settings: None,
276        }
277    }
278
279    pub fn reset(&mut self) {
280        self.functions.clear();
281        self.event_source_mappings.clear();
282        self.invocations.clear();
283        self.aliases.clear();
284        self.function_versions.clear();
285        self.layers.clear();
286        self.function_url_configs.clear();
287        self.function_concurrency.clear();
288        self.provisioned_concurrency.clear();
289        self.code_signing_configs.clear();
290        self.function_code_signing.clear();
291        self.event_invoke_configs.clear();
292        self.runtime_management.clear();
293        self.scaling_configs.clear();
294        self.recursion_configs.clear();
295        self.tags.clear();
296        self.capacity_providers.clear();
297        self.durable_executions.clear();
298        self.account_settings = None;
299    }
300}
301
302pub type SharedLambdaState =
303    Arc<RwLock<fakecloud_core::multi_account::MultiAccountState<LambdaState>>>;
304
305impl fakecloud_core::multi_account::AccountState for LambdaState {
306    fn new_for_account(account_id: &str, region: &str, _endpoint: &str) -> Self {
307        Self::new(account_id, region)
308    }
309}
310
311pub const LAMBDA_SNAPSHOT_SCHEMA_VERSION: u32 = 2;
312
313#[derive(Debug, Serialize, Deserialize)]
314pub struct LambdaSnapshot {
315    pub schema_version: u32,
316    #[serde(default)]
317    pub accounts: Option<fakecloud_core::multi_account::MultiAccountState<LambdaState>>,
318    #[serde(default)]
319    pub state: Option<LambdaState>,
320}
321
322#[cfg(test)]
323mod tests {
324    use super::*;
325
326    #[test]
327    fn new_has_empty_collections() {
328        let state = LambdaState::new("123456789012", "us-east-1");
329        assert_eq!(state.account_id, "123456789012");
330        assert_eq!(state.region, "us-east-1");
331        assert!(state.functions.is_empty());
332        assert!(state.event_source_mappings.is_empty());
333        assert!(state.invocations.is_empty());
334    }
335
336    #[test]
337    fn reset_clears_collections() {
338        let mut state = LambdaState::new("123456789012", "us-east-1");
339        state.invocations.push(LambdaInvocation {
340            function_arn: "arn".to_string(),
341            payload: "p".to_string(),
342            timestamp: Utc::now(),
343            source: "s".to_string(),
344        });
345        state.reset();
346        assert!(state.invocations.is_empty());
347    }
348}