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:
parent
e84b801c2e
commit
59e94227af
@ -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
97
src/tools/serde_filter.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user