Several objects, statements and expressions in nftables-json require null values, for instance:
{ "flush": { "ruleset": null }} For this purpose we define our own Null type, which we can then easily use for defining types that accept Null as value. Several keys accept as value either a singular element (string or object) if there is only one object, but an array if there are multiple objects. For instance when adding a single element to a set: { "element": { ... "elem": "element1" }} but when adding multiple elements: { "element": { ... "elem": ["element1", "element2"] }} NfVec<T> is a wrapper for Vec<T> that serializes into T iff Vec contains one element, otherwise it serializes like a Vec would normally do. Reviewed-by: Lukas Wagner <l.wag...@proxmox.com> Reviewed-by: Max Carrara <m.carr...@proxmox.com> Co-authored-by: Wolfgang Bumiller <w.bumil...@proxmox.com> Signed-off-by: Stefan Hanreich <s.hanre...@proxmox.com> --- proxmox-nftables/Cargo.toml | 4 + proxmox-nftables/src/helper.rs | 190 +++++++++++++++++++++++++++++++++ proxmox-nftables/src/lib.rs | 1 + 3 files changed, 195 insertions(+) create mode 100644 proxmox-nftables/src/helper.rs diff --git a/proxmox-nftables/Cargo.toml b/proxmox-nftables/Cargo.toml index 764e231..ebece9d 100644 --- a/proxmox-nftables/Cargo.toml +++ b/proxmox-nftables/Cargo.toml @@ -13,4 +13,8 @@ license = "AGPL-3" [dependencies] log = "0.4" +serde = { version = "1", features = [ "derive" ] } +serde_json = "1" +serde_plain = "1" + proxmox-ve-config = { path = "../proxmox-ve-config", optional = true } diff --git a/proxmox-nftables/src/helper.rs b/proxmox-nftables/src/helper.rs new file mode 100644 index 0000000..77ce347 --- /dev/null +++ b/proxmox-nftables/src/helper.rs @@ -0,0 +1,190 @@ +use std::fmt; +use std::marker::PhantomData; + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Copy, Debug)] +pub struct Null; + +impl<'de> Deserialize<'de> for Null { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + use serde::de::Error; + + match Option::<()>::deserialize(deserializer)? { + None => Ok(Self), + Some(_) => Err(D::Error::custom("expected null")), + } + } +} + +impl Serialize for Null { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + serializer.serialize_none() + } +} + +impl fmt::Display for Null { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("null") + } +} + +#[derive(Clone, Debug)] +pub struct NfVec<T>(pub(crate) Vec<T>); + +impl<T> Default for NfVec<T> { + fn default() -> Self { + Self::new() + } +} + +impl<T> NfVec<T> { + pub const fn new() -> Self { + Self(Vec::new()) + } + + pub fn one(value: T) -> Self { + Self(vec![value]) + } +} + +impl<T> From<Vec<T>> for NfVec<T> { + fn from(v: Vec<T>) -> Self { + Self(v) + } +} + +impl<T> From<NfVec<T>> for Vec<T> { + fn from(v: NfVec<T>) -> Self { + v.0 + } +} + +impl<T> FromIterator<T> for NfVec<T> { + fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self { + Self(iter.into_iter().collect()) + } +} + +impl<T> std::ops::Deref for NfVec<T> { + type Target = Vec<T>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<T> std::ops::DerefMut for NfVec<T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<T: Serialize> Serialize for NfVec<T> { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + if self.len() == 1 { + self[0].serialize(serializer) + } else { + self.0.serialize(serializer) + } + } +} + +macro_rules! visit_value { + ($( ($visit:ident, $($ty:tt)+), )+) => { + $( + fn $visit<E>(self, value: $($ty)+) -> Result<Self::Value, E> + where + E: Error, + { + T::deserialize(value.into_deserializer()).map(NfVec::one) + } + )+ + }; +} + +impl<'de, T: Deserialize<'de>> Deserialize<'de> for NfVec<T> { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + use serde::de::{Error, IntoDeserializer}; + + struct V<T>(PhantomData<T>); + + impl<'de, T: Deserialize<'de>> serde::de::Visitor<'de> for V<T> { + type Value = NfVec<T>; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("an array or single element") + } + + fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error> + where + A: serde::de::SeqAccess<'de>, + { + Vec::<T>::deserialize(serde::de::value::SeqAccessDeserializer::new(seq)).map(NfVec) + } + + fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error> + where + A: serde::de::MapAccess<'de>, + { + T::deserialize(serde::de::value::MapAccessDeserializer::new(map)).map(NfVec::one) + } + + fn visit_none<E>(self) -> Result<Self::Value, E> + where + E: Error, + { + Ok(NfVec::new()) + } + + fn visit_unit<E>(self) -> Result<Self::Value, E> + where + E: Error, + { + Ok(NfVec::new()) + } + + fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error> + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_any(self) + } + + visit_value! { + (visit_bool, bool), + (visit_borrowed_bytes, &'de [u8]), + (visit_borrowed_str, &'de str), + (visit_byte_buf, Vec<u8>), + (visit_bytes, &[u8]), + (visit_char, char), + (visit_f32, f32), + (visit_f64, f64), + (visit_i8, i8), + (visit_i16, i16), + (visit_i32, i32), + (visit_i64, i64), + (visit_u8, u8), + (visit_u16, u16), + (visit_u32, u32), + (visit_u64, u64), + (visit_str, &str), + (visit_string, String), + } + } + + deserializer.deserialize_any(V::<T>(PhantomData)) + } +} diff --git a/proxmox-nftables/src/lib.rs b/proxmox-nftables/src/lib.rs index e69de29..485bb81 100644 --- a/proxmox-nftables/src/lib.rs +++ b/proxmox-nftables/src/lib.rs @@ -0,0 +1 @@ +pub mod helper; -- 2.39.2 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel