Skip to main content

inset_embedder/
restoration.rs

1//! The restoration data the framework hands the host and gets back.
2//!
3//! Flutter's restoration data is a `Map<Object?, Object?>` that travels over
4//! `SystemChannels.restoration` encoded with `StandardMessageCodec`. There is no channel
5//! here, so the data stays a value: [`RestorationData`] is one value of the kinds that
6//! codec can carry, and [`RestorationMap`] is the map itself.
7
8use std::hash::{Hash, Hasher};
9
10use indexmap::IndexMap;
11
12/// Flutter's `Map<Object?, Object?>` of restoration data: insertion-ordered, keyed by value.
13pub type RestorationMap = IndexMap<RestorationData, RestorationData>;
14
15/// One value in the restoration data, of the kinds `StandardMessageCodec` can carry.
16///
17/// [`Double`](Self::Double) compares and hashes by IEEE equality, so `0.0` and `-0.0` are
18/// one key and a `NaN` key can never be looked up again — as in Dart.
19#[derive(Clone, Debug, Default, PartialEq)]
20pub enum RestorationData {
21    /// Dart's `null`.
22    #[default]
23    Null,
24    /// Dart's `bool`.
25    Bool(bool),
26    /// Dart's `int`.
27    Int(i64),
28    /// Dart's `double`.
29    Double(f64),
30    /// Dart's `String`.
31    String(String),
32    /// Dart's `Uint8List`.
33    Uint8List(Vec<u8>),
34    /// Dart's `Int32List`.
35    Int32List(Vec<i32>),
36    /// Dart's `Int64List`.
37    Int64List(Vec<i64>),
38    /// Dart's `Float64List`.
39    Float64List(Vec<f64>),
40    /// Dart's `List<Object?>`.
41    List(Vec<RestorationData>),
42    /// Dart's `Map<Object?, Object?>`.
43    Map(RestorationMap),
44}
45
46impl RestorationData {
47    /// Whether this is [`Null`](Self::Null).
48    pub fn is_null(&self) -> bool {
49        matches!(self, RestorationData::Null)
50    }
51
52    /// The `bool`, or `None` for another kind.
53    pub fn as_bool(&self) -> Option<bool> {
54        match self {
55            RestorationData::Bool(value) => Some(*value),
56            _ => None,
57        }
58    }
59
60    /// The `int`, or `None` for another kind.
61    pub fn as_int(&self) -> Option<i64> {
62        match self {
63            RestorationData::Int(value) => Some(*value),
64            _ => None,
65        }
66    }
67
68    /// The `double`, or `None` for another kind.
69    pub fn as_double(&self) -> Option<f64> {
70        match self {
71            RestorationData::Double(value) => Some(*value),
72            _ => None,
73        }
74    }
75
76    /// The `String`, or `None` for another kind.
77    pub fn as_str(&self) -> Option<&str> {
78        match self {
79            RestorationData::String(value) => Some(value),
80            _ => None,
81        }
82    }
83
84    /// The `Uint8List`, or `None` for another kind.
85    pub fn as_uint8_list(&self) -> Option<&[u8]> {
86        match self {
87            RestorationData::Uint8List(values) => Some(values),
88            _ => None,
89        }
90    }
91
92    /// The `Int32List`, or `None` for another kind.
93    pub fn as_int32_list(&self) -> Option<&[i32]> {
94        match self {
95            RestorationData::Int32List(values) => Some(values),
96            _ => None,
97        }
98    }
99
100    /// The `Int64List`, or `None` for another kind.
101    pub fn as_int64_list(&self) -> Option<&[i64]> {
102        match self {
103            RestorationData::Int64List(values) => Some(values),
104            _ => None,
105        }
106    }
107
108    /// The `Float64List`, or `None` for another kind.
109    pub fn as_float64_list(&self) -> Option<&[f64]> {
110        match self {
111            RestorationData::Float64List(values) => Some(values),
112            _ => None,
113        }
114    }
115
116    /// The list, or `None` for another kind.
117    pub fn as_list(&self) -> Option<&[RestorationData]> {
118        match self {
119            RestorationData::List(values) => Some(values),
120            _ => None,
121        }
122    }
123
124    /// The map, or `None` for another kind.
125    pub fn as_map(&self) -> Option<&RestorationMap> {
126        match self {
127            RestorationData::Map(map) => Some(map),
128            _ => None,
129        }
130    }
131
132    /// The map to write into, or `None` for another kind.
133    pub fn as_map_mut(&mut self) -> Option<&mut RestorationMap> {
134        match self {
135            RestorationData::Map(map) => Some(map),
136            _ => None,
137        }
138    }
139
140    /// Takes the map out, or `None` for another kind.
141    pub fn into_map(self) -> Option<RestorationMap> {
142        match self {
143            RestorationData::Map(map) => Some(map),
144            _ => None,
145        }
146    }
147}
148
149/// `NaN` hashes by its bits and `-0.0` hashes as `0.0`, so hashing agrees with IEEE equality.
150fn hash_double<H: Hasher>(value: f64, state: &mut H) {
151    let normalized = if value == 0.0 { 0.0 } else { value };
152    normalized.to_bits().hash(state);
153}
154
155impl Eq for RestorationData {}
156
157impl Hash for RestorationData {
158    fn hash<H: Hasher>(&self, state: &mut H) {
159        std::mem::discriminant(self).hash(state);
160        match self {
161            RestorationData::Null => {}
162            RestorationData::Bool(value) => value.hash(state),
163            RestorationData::Int(value) => value.hash(state),
164            RestorationData::Double(value) => hash_double(*value, state),
165            RestorationData::String(value) => value.hash(state),
166            RestorationData::Uint8List(values) => values.hash(state),
167            RestorationData::Int32List(values) => values.hash(state),
168            RestorationData::Int64List(values) => values.hash(state),
169            RestorationData::Float64List(values) => {
170                for value in values {
171                    hash_double(*value, state);
172                }
173            }
174            RestorationData::List(values) => values.hash(state),
175            // Two maps are equal whatever order they were built in, so only the size
176            // can take part in the hash.
177            RestorationData::Map(map) => map.len().hash(state),
178        }
179    }
180}
181
182impl From<bool> for RestorationData {
183    fn from(value: bool) -> RestorationData {
184        RestorationData::Bool(value)
185    }
186}
187
188impl From<i64> for RestorationData {
189    fn from(value: i64) -> RestorationData {
190        RestorationData::Int(value)
191    }
192}
193
194impl From<f64> for RestorationData {
195    fn from(value: f64) -> RestorationData {
196        RestorationData::Double(value)
197    }
198}
199
200impl From<&str> for RestorationData {
201    fn from(value: &str) -> RestorationData {
202        RestorationData::String(value.to_string())
203    }
204}
205
206impl From<String> for RestorationData {
207    fn from(value: String) -> RestorationData {
208        RestorationData::String(value)
209    }
210}
211
212impl From<Vec<u8>> for RestorationData {
213    fn from(values: Vec<u8>) -> RestorationData {
214        RestorationData::Uint8List(values)
215    }
216}
217
218impl From<Vec<i32>> for RestorationData {
219    fn from(values: Vec<i32>) -> RestorationData {
220        RestorationData::Int32List(values)
221    }
222}
223
224impl From<Vec<i64>> for RestorationData {
225    fn from(values: Vec<i64>) -> RestorationData {
226        RestorationData::Int64List(values)
227    }
228}
229
230impl From<Vec<f64>> for RestorationData {
231    fn from(values: Vec<f64>) -> RestorationData {
232        RestorationData::Float64List(values)
233    }
234}
235
236impl From<Vec<RestorationData>> for RestorationData {
237    fn from(values: Vec<RestorationData>) -> RestorationData {
238        RestorationData::List(values)
239    }
240}
241
242impl From<RestorationMap> for RestorationData {
243    fn from(map: RestorationMap) -> RestorationData {
244        RestorationData::Map(map)
245    }
246}
247
248impl<T: Into<RestorationData>> From<Option<T>> for RestorationData {
249    fn from(value: Option<T>) -> RestorationData {
250        match value {
251            Some(value) => value.into(),
252            None => RestorationData::Null,
253        }
254    }
255}
256
257/// What the host answers when the framework asks for the restoration data it stored.
258///
259/// Flutter's `get` on `SystemChannels.restoration` replies with a map of the same two
260/// entries.
261#[derive(Clone, Debug, Default, PartialEq)]
262pub struct RestorationUpdate {
263    /// Whether the host wants restoration data at all. When false, state restoration is
264    /// turned off and the application has no root bucket.
265    pub enabled: bool,
266
267    /// The data the host stored the last time the framework handed it some. `None` starts
268    /// the application with an empty root bucket.
269    pub data: Option<RestorationMap>,
270}
271
272#[cfg(test)]
273mod tests {
274    use std::collections::hash_map::DefaultHasher;
275    use std::hash::{Hash, Hasher};
276
277    use super::{RestorationData, RestorationMap};
278
279    fn hash_of(value: &RestorationData) -> u64 {
280        let mut hasher = DefaultHasher::new();
281        value.hash(&mut hasher);
282        hasher.finish()
283    }
284
285    #[test]
286    fn zero_and_negative_zero_are_one_key() {
287        let zero = RestorationData::from(0.0);
288        let negative_zero = RestorationData::from(-0.0);
289        assert_eq!(zero, negative_zero);
290        assert_eq!(hash_of(&zero), hash_of(&negative_zero));
291    }
292
293    #[test]
294    fn a_map_keyed_by_value_finds_what_it_stored() {
295        let mut map = RestorationMap::new();
296        map.insert("value1".into(), 10i64.into());
297        map.insert(12i64.into(), vec![1.5f64, 2.5].into());
298
299        assert_eq!(
300            map.get(&RestorationData::from("value1")).unwrap().as_int(),
301            Some(10)
302        );
303        assert_eq!(
304            map.get(&RestorationData::from(12i64))
305                .unwrap()
306                .as_float64_list(),
307            Some([1.5, 2.5].as_slice())
308        );
309        assert!(map.get(&RestorationData::from("missing")).is_none());
310    }
311
312    #[test]
313    fn an_optional_value_becomes_null() {
314        assert!(RestorationData::from(None::<i64>).is_null());
315        assert_eq!(RestorationData::from(Some(3i64)).as_int(), Some(3));
316    }
317}