add tools::serde_filter submodule

can be used to perform filtering at parse time

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2020-11-02 11:52:03 +01:00
parent e84b801c2e
commit 59e94227af
2 changed files with 98 additions and 0 deletions

View File

@ -33,6 +33,7 @@ pub mod loopdev;
pub mod lru_cache; pub mod lru_cache;
pub mod nom; pub mod nom;
pub mod runtime; pub mod runtime;
pub mod serde_filter;
pub mod socket; pub mod socket;
pub mod statistics; pub mod statistics;
pub mod subscription; pub mod subscription;

97
src/tools/serde_filter.rs Normal file
View File

@ -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)
}
}