[pbs-devel] [RFC backup 1/6] add tools::serde_filter submodule

Wolfgang Bumiller w.bumiller at proxmox.com
Thu Nov 19 15:56:03 CET 2020


can be used to perform filtering at parse time

Signed-off-by: Wolfgang Bumiller <w.bumiller at proxmox.com>
---
 src/tools.rs              |  1 +
 src/tools/serde_filter.rs | 97 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 98 insertions(+)
 create mode 100644 src/tools/serde_filter.rs

diff --git a/src/tools.rs b/src/tools.rs
index 08f9d22f..8cc446dd 100644
--- a/src/tools.rs
+++ b/src/tools.rs
@@ -32,6 +32,7 @@ pub mod loopdev;
 pub mod lru_cache;
 pub mod nom;
 pub mod runtime;
+pub mod serde_filter;
 pub mod socket;
 pub mod statistics;
 pub mod subscription;
diff --git a/src/tools/serde_filter.rs b/src/tools/serde_filter.rs
new file mode 100644
index 00000000..b8402696
--- /dev/null
+++ b/src/tools/serde_filter.rs
@@ -0,0 +1,97 @@
+use std::marker::PhantomData;
+
+use serde::Deserialize;
+
+/// Helper to filter data while deserializing it.
+///
+/// An example use case is filtering out expired registration challenges at load time of our TFA
+/// config:
+///
+/// ```
+/// # use proxmox_backup::tools::serde_filter::FilteredVecVisitor;
+/// # use serde::{Deserialize, Deserializer, Serialize};
+/// # const CHALLENGE_TIMEOUT: i64 = 2 * 60;
+/// #[derive(Deserialize)]
+/// struct Challenge {
+///     /// Expiration time as unix epoch.
+///     expires: i64,
+///
+///     // ...other entries...
+/// }
+///
+/// #[derive(Default, Deserialize)]
+/// #[serde(deny_unknown_fields)]
+/// #[serde(rename_all = "kebab-case")]
+/// pub struct TfaUserData {
+///     // ...other entries...
+///
+///     #[serde(skip_serializing_if = "Vec::is_empty", default)]
+///     #[serde(deserialize_with = "filter_expired_registrations")]
+///     registrations: Vec<Challenge>,
+/// }
+///
+/// fn filter_expired_registrations<'de, D>(deserializer: D) -> Result<Vec<Challenge>, D::Error>
+/// where
+///     D: Deserializer<'de>,
+/// {
+///     let expire_before = proxmox::tools::time::epoch_i64() - CHALLENGE_TIMEOUT;
+///
+///     Ok(deserializer.deserialize_seq(
+///         FilteredVecVisitor::new(
+///             "a u2f registration challenge entry",
+///             move |c: &Challenge| c.expires < expire_before,
+///         )
+///     )?)
+/// }
+/// ```
+pub struct FilteredVecVisitor<F, T>
+where
+    F: Fn(&T) -> bool
+{
+    filter: F,
+    expecting: &'static str,
+    _ty: PhantomData<T>,
+}
+
+impl<F, T> FilteredVecVisitor<F, T>
+where
+    F: Fn(&T) -> bool,
+{
+    pub fn new(expecting: &'static str, filter: F) -> Self {
+        Self {
+            filter,
+            expecting,
+            _ty: PhantomData,
+        }
+    }
+}
+
+impl<'de, F, T> serde::de::Visitor<'de> for FilteredVecVisitor<F, T>
+where
+    F: Fn(&T) -> bool,
+    T: Deserialize<'de>,
+{
+    type Value = Vec<T>;
+
+    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+        formatter.write_str(self.expecting)
+    }
+
+    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
+    where
+        A: serde::de::SeqAccess<'de>,
+    {
+        let mut out = match seq.size_hint() {
+            Some(hint) => Vec::with_capacity(hint),
+            None => Vec::new(),
+        };
+
+        while let Some(entry) = seq.next_element::<T>()? {
+            if (self.filter)(&entry) {
+                out.push(entry);
+            }
+        }
+
+        Ok(out)
+    }
+}
-- 
2.20.1






More information about the pbs-devel mailing list