fakecloud_iotdata/state.rs
1//! Account-partitioned, serializable state for the AWS IoT Data Plane
2//! (`iotdata`).
3//!
4//! Two stores per account:
5//!
6//! * `shadows` — device shadows, keyed by `thingName`. Each thing owns a map
7//! of `shadowName` -> shadow document, where the empty string `""` is the
8//! classic (unnamed) shadow and any other key is a named shadow. Every key
9//! is a plain `String`, so the snapshot never depends on the tuple-key serde
10//! adapter.
11//! * `retained` — retained MQTT messages, keyed by `topic`. The stored value
12//! carries the base64 payload plus `qos` / `lastModifiedTime`.
13
14use std::collections::BTreeMap;
15use std::sync::Arc;
16
17use parking_lot::RwLock;
18use serde::{Deserialize, Serialize};
19use serde_json::Value;
20
21use fakecloud_core::multi_account::{AccountState, MultiAccountState};
22
23pub const IOTDATA_SNAPSHOT_SCHEMA_VERSION: u32 = 1;
24
25/// The classic (unnamed) shadow is stored under this reserved shadow-name key.
26pub const CLASSIC_SHADOW_KEY: &str = "";
27
28/// Per-account IoT Data Plane state.
29#[derive(Debug, Clone, Default, Serialize, Deserialize)]
30pub struct IotDataData {
31 /// Device shadows keyed by `thingName`, then by `shadowName` (with the
32 /// empty string denoting the classic shadow). The stored value is the full
33 /// internal shadow document (`state` / `metadata` / `version` /
34 /// `timestamp`).
35 #[serde(default)]
36 pub shadows: BTreeMap<String, BTreeMap<String, Value>>,
37 /// Retained MQTT messages keyed by `topic`. The stored value carries the
38 /// base64 `payload`, `qos`, and `lastModifiedTime` (epoch millis).
39 #[serde(default)]
40 pub retained: BTreeMap<String, Value>,
41}
42
43impl AccountState for IotDataData {
44 fn new_for_account(_account_id: &str, _region: &str, _endpoint: &str) -> Self {
45 Self::default()
46 }
47}
48
49pub type SharedIotDataState = Arc<RwLock<MultiAccountState<IotDataData>>>;
50
51#[derive(Debug, Serialize, Deserialize)]
52pub struct IotDataSnapshot {
53 pub schema_version: u32,
54 pub accounts: MultiAccountState<IotDataData>,
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60
61 #[test]
62 fn new_account_is_empty() {
63 let data = IotDataData::new_for_account("000000000000", "us-east-1", "");
64 assert!(data.shadows.is_empty());
65 assert!(data.retained.is_empty());
66 }
67}