Skip to main content

fakecloud_lambda/
state.rs

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