]>
Commit | Line | Data |
---|---|---|
59e94227 WB |
1 | use std::marker::PhantomData; |
2 | ||
3 | use serde::Deserialize; | |
4 | ||
5 | /// Helper to filter data while deserializing it. | |
6 | /// | |
7 | /// An example use case is filtering out expired registration challenges at load time of our TFA | |
8 | /// config: | |
9 | /// | |
10 | /// ``` | |
11 | /// # use proxmox_backup::tools::serde_filter::FilteredVecVisitor; | |
12 | /// # use serde::{Deserialize, Deserializer, Serialize}; | |
13 | /// # const CHALLENGE_TIMEOUT: i64 = 2 * 60; | |
14 | /// #[derive(Deserialize)] | |
15 | /// struct Challenge { | |
16 | /// /// Expiration time as unix epoch. | |
17 | /// expires: i64, | |
18 | /// | |
19 | /// // ...other entries... | |
20 | /// } | |
21 | /// | |
22 | /// #[derive(Default, Deserialize)] | |
23 | /// #[serde(deny_unknown_fields)] | |
24 | /// #[serde(rename_all = "kebab-case")] | |
25 | /// pub struct TfaUserData { | |
26 | /// // ...other entries... | |
27 | /// | |
28 | /// #[serde(skip_serializing_if = "Vec::is_empty", default)] | |
29 | /// #[serde(deserialize_with = "filter_expired_registrations")] | |
30 | /// registrations: Vec<Challenge>, | |
31 | /// } | |
32 | /// | |
33 | /// fn filter_expired_registrations<'de, D>(deserializer: D) -> Result<Vec<Challenge>, D::Error> | |
34 | /// where | |
35 | /// D: Deserializer<'de>, | |
36 | /// { | |
6ef1b649 | 37 | /// let expire_before = proxmox_time::epoch_i64() - CHALLENGE_TIMEOUT; |
59e94227 WB |
38 | /// |
39 | /// Ok(deserializer.deserialize_seq( | |
40 | /// FilteredVecVisitor::new( | |
41 | /// "a u2f registration challenge entry", | |
42 | /// move |c: &Challenge| c.expires < expire_before, | |
43 | /// ) | |
44 | /// )?) | |
45 | /// } | |
46 | /// ``` | |
47 | pub struct FilteredVecVisitor<F, T> | |
48 | where | |
49 | F: Fn(&T) -> bool | |
50 | { | |
51 | filter: F, | |
52 | expecting: &'static str, | |
53 | _ty: PhantomData<T>, | |
54 | } | |
55 | ||
56 | impl<F, T> FilteredVecVisitor<F, T> | |
57 | where | |
58 | F: Fn(&T) -> bool, | |
59 | { | |
60 | pub fn new(expecting: &'static str, filter: F) -> Self { | |
61 | Self { | |
62 | filter, | |
63 | expecting, | |
64 | _ty: PhantomData, | |
65 | } | |
66 | } | |
67 | } | |
68 | ||
69 | impl<'de, F, T> serde::de::Visitor<'de> for FilteredVecVisitor<F, T> | |
70 | where | |
71 | F: Fn(&T) -> bool, | |
72 | T: Deserialize<'de>, | |
73 | { | |
74 | type Value = Vec<T>; | |
75 | ||
76 | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { | |
77 | formatter.write_str(self.expecting) | |
78 | } | |
79 | ||
80 | fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> | |
81 | where | |
82 | A: serde::de::SeqAccess<'de>, | |
83 | { | |
84 | let mut out = match seq.size_hint() { | |
85 | Some(hint) => Vec::with_capacity(hint), | |
86 | None => Vec::new(), | |
87 | }; | |
88 | ||
89 | while let Some(entry) = seq.next_element::<T>()? { | |
90 | if (self.filter)(&entry) { | |
91 | out.push(entry); | |
92 | } | |
93 | } | |
94 | ||
95 | Ok(out) | |
96 | } | |
97 | } |