[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