From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id B997B1FF1A6 for ; Tue, 13 May 2025 12:15:12 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 3C3221E64E; Tue, 13 May 2025 12:15:34 +0200 (CEST) From: Stefan Hanreich To: pbs-devel@lists.proxmox.com Date: Tue, 13 May 2025 12:14:54 +0200 Message-Id: <20250513101459.122641-2-s.hanreich@proxmox.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250513101459.122641-1-s.hanreich@proxmox.com> References: <20250513101459.122641-1-s.hanreich@proxmox.com> MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.232 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods RDNS_NONE 0.793 Delivered to internal network by a host with no rDNS SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_NONE 0.001 SPF: sender does not publish an SPF Record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [lib.rs, perl.rs] Subject: [pbs-devel] [PATCH proxmox 1/3] serde: add parsing helpers for perl X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox Backup Server development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pbs-devel-bounces@lists.proxmox.com Sender: "pbs-devel" Those have been moved from the proxmox-login crate. This crate seems like a more natural place for hosting helpers to parse data coming from perl via serde. Signed-off-by: Stefan Hanreich --- proxmox-serde/Cargo.toml | 3 + proxmox-serde/src/lib.rs | 3 + proxmox-serde/src/perl.rs | 373 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 379 insertions(+) create mode 100644 proxmox-serde/src/perl.rs diff --git a/proxmox-serde/Cargo.toml b/proxmox-serde/Cargo.toml index aa77099a..b1edb60d 100644 --- a/proxmox-serde/Cargo.toml +++ b/proxmox-serde/Cargo.toml @@ -20,3 +20,6 @@ proxmox-time.workspace = true [dev-dependencies] serde_json.workspace = true + +[features] +perl = [] diff --git a/proxmox-serde/src/lib.rs b/proxmox-serde/src/lib.rs index c1628349..8a3d8794 100644 --- a/proxmox-serde/src/lib.rs +++ b/proxmox-serde/src/lib.rs @@ -8,6 +8,9 @@ pub mod serde_macros; #[cfg(feature = "serde_json")] pub mod json; +#[cfg(feature = "perl")] +pub mod perl; + /// Serialize Unix epoch (i64) as RFC3339. /// /// Usage example: diff --git a/proxmox-serde/src/perl.rs b/proxmox-serde/src/perl.rs new file mode 100644 index 00000000..8efa86c9 --- /dev/null +++ b/proxmox-serde/src/perl.rs @@ -0,0 +1,373 @@ +//! Some parsing helpers for the PVE API, mainly to deal with perl's untypedness. + +use std::fmt; + +use serde::de::Unexpected; + +// Boolean: + +pub trait FromBool: Sized + Default { + fn from_bool(value: bool) -> Self; +} + +impl FromBool for bool { + fn from_bool(value: bool) -> Self { + value + } +} + +impl FromBool for Option { + fn from_bool(value: bool) -> Self { + Some(value) + } +} + +pub fn deserialize_bool<'de, D, T>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, + T: FromBool, +{ + deserializer.deserialize_any(BoolVisitor::::new()) +} + +struct BoolVisitor(std::marker::PhantomData); + +impl BoolVisitor { + fn new() -> Self { + Self(std::marker::PhantomData) + } +} + +impl<'de, T: FromBool> serde::de::DeserializeSeed<'de> for BoolVisitor { + type Value = T; + + fn deserialize(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserialize_bool(deserializer) + } +} + +impl<'de, T> serde::de::Visitor<'de> for BoolVisitor +where + T: FromBool, +{ + type Value = T; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("a boolean-ish...") + } + + fn visit_some(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_any(self) + } + + fn visit_none(self) -> Result { + Ok(Default::default()) + } + + fn visit_bool(self, value: bool) -> Result { + Ok(Self::Value::from_bool(value)) + } + + fn visit_i128(self, value: i128) -> Result { + Ok(Self::Value::from_bool(value != 0)) + } + + fn visit_i64(self, value: i64) -> Result { + Ok(Self::Value::from_bool(value != 0)) + } + + fn visit_u64(self, value: u64) -> Result { + Ok(Self::Value::from_bool(value != 0)) + } + + fn visit_u128(self, value: u128) -> Result { + Ok(Self::Value::from_bool(value != 0)) + } + + fn visit_str(self, value: &str) -> Result { + let value = if value.eq_ignore_ascii_case("true") + || value.eq_ignore_ascii_case("yes") + || value.eq_ignore_ascii_case("on") + || value == "1" + { + true + } else if value.eq_ignore_ascii_case("false") + || value.eq_ignore_ascii_case("no") + || value.eq_ignore_ascii_case("off") + || value == "0" + { + false + } else { + return Err(E::invalid_value( + serde::de::Unexpected::Str(value), + &"a boolean-like value", + )); + }; + Ok(Self::Value::from_bool(value)) + } +} + +// integer helpers: + +macro_rules! integer_helper { + ($ty:ident, $deserialize_name:ident, $trait: ident, $from_name:ident, $visitor:ident) => { + #[doc(hidden)] + pub trait $trait: Sized + Default { + fn $from_name(value: $ty) -> Self; + } + + impl $trait for $ty { + fn $from_name(value: $ty) -> Self { + value + } + } + + impl $trait for Option<$ty> { + fn $from_name(value: $ty) -> Self { + Some(value) + } + } + + pub fn $deserialize_name<'de, D, T>(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + T: $trait, + { + deserializer.deserialize_any($visitor::::new()) + } + + struct $visitor(std::marker::PhantomData); + + impl $visitor { + fn new() -> Self { + Self(std::marker::PhantomData) + } + } + + impl<'de, T: $trait> serde::de::DeserializeSeed<'de> for $visitor { + type Value = T; + + fn deserialize(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + $deserialize_name(deserializer) + } + } + + impl<'de, T> serde::de::Visitor<'de> for $visitor + where + T: $trait, + { + type Value = T; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(concat!("a ", stringify!($ty), "-ish...")) + } + + fn visit_some(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_any(self) + } + + fn visit_none(self) -> Result { + Ok(Default::default()) + } + + fn visit_i128(self, value: i128) -> Result { + $ty::try_from(value) + .map_err(|_| E::invalid_value(Unexpected::Other("i128"), &self)) + .map(Self::Value::$from_name) + } + + fn visit_i64(self, value: i64) -> Result { + $ty::try_from(value) + .map_err(|_| E::invalid_value(Unexpected::Signed(value), &self)) + .map(Self::Value::$from_name) + } + + fn visit_u64(self, value: u64) -> Result { + $ty::try_from(value) + .map_err(|_| E::invalid_value(Unexpected::Unsigned(value), &self)) + .map(Self::Value::$from_name) + } + + fn visit_u128(self, value: u128) -> Result { + $ty::try_from(value) + .map_err(|_| E::invalid_value(Unexpected::Other("u128"), &self)) + .map(Self::Value::$from_name) + } + + fn visit_str(self, value: &str) -> Result { + let value = value + .parse() + .map_err(|_| E::invalid_value(Unexpected::Str(value), &self))?; + self.visit_i64(value) + } + } + }; +} + +integer_helper!( + isize, + deserialize_isize, + FromIsize, + from_isize, + IsizeVisitor +); + +integer_helper!( + usize, + deserialize_usize, + FromUsize, + from_usize, + UsizeVisitor +); + +integer_helper!(u8, deserialize_u8, FromU8, from_u8, U8Visitor); +integer_helper!(u16, deserialize_u16, FromU16, from_u16, U16Visitor); +integer_helper!(u32, deserialize_u32, FromU32, from_u32, U32Visitor); +integer_helper!(u64, deserialize_u64, FromU64, from_u64, U64Visitor); +integer_helper!(i8, deserialize_i8, FromI8, from_i8, I8Visitor); +integer_helper!(i16, deserialize_i16, FromI16, from_i16, I16Visitor); +integer_helper!(i32, deserialize_i32, FromI32, from_i32, I32Visitor); +integer_helper!(i64, deserialize_i64, FromI64, from_i64, I64Visitor); + +// float helpers: + +macro_rules! float_helper { + ($ty:ident, $deserialize_name:ident, $visitor:ident) => { + pub fn $deserialize_name<'de, D, T>(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + T: FromF64, + { + deserializer.deserialize_any($visitor::::new()) + } + + struct $visitor(std::marker::PhantomData); + + impl $visitor { + fn new() -> Self { + Self(std::marker::PhantomData) + } + } + + impl<'de, T: FromF64> serde::de::DeserializeSeed<'de> for $visitor { + type Value = T; + + fn deserialize(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + $deserialize_name(deserializer) + } + } + + impl<'de, T> serde::de::Visitor<'de> for $visitor + where + T: FromF64, + { + type Value = T; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(concat!("a ", stringify!($ty), "-ish...")) + } + + fn visit_some(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_any(self) + } + + fn visit_none(self) -> Result { + Ok(Default::default()) + } + + fn visit_f64(self, value: f64) -> Result { + Ok(T::from_f64(value)) + } + + fn visit_i128(self, value: i128) -> Result { + let conv = value as f64; + if conv as i128 == value { + Ok(T::from_f64(conv)) + } else { + Err(E::invalid_value(Unexpected::Other("i128"), &self)) + } + } + + fn visit_i64(self, value: i64) -> Result { + let conv = value as f64; + if conv as i64 == value { + Ok(T::from_f64(conv)) + } else { + Err(E::invalid_value(Unexpected::Signed(value), &self)) + } + } + + fn visit_u128(self, value: u128) -> Result { + let conv = value as f64; + if conv as u128 == value { + Ok(T::from_f64(conv)) + } else { + Err(E::invalid_value(Unexpected::Other("u128"), &self)) + } + } + + fn visit_u64(self, value: u64) -> Result { + let conv = value as f64; + if conv as u64 == value { + Ok(T::from_f64(conv)) + } else { + Err(E::invalid_value(Unexpected::Unsigned(value), &self)) + } + } + + fn visit_str(self, value: &str) -> Result { + let value = value + .parse() + .map_err(|_| E::invalid_value(Unexpected::Str(value), &self))?; + self.visit_f64(value) + } + } + }; +} + +#[doc(hidden)] +pub trait FromF64: Sized + Default { + fn from_f64(value: f64) -> Self; +} + +impl FromF64 for f32 { + #[inline(always)] + fn from_f64(f: f64) -> f32 { + f as f32 + } +} + +impl FromF64 for f64 { + #[inline(always)] + fn from_f64(f: f64) -> f64 { + f + } +} + +impl FromF64 for Option { + #[inline(always)] + fn from_f64(f: f64) -> Option { + Some(T::from_f64(f)) + } +} + +float_helper!(f32, deserialize_f32, F32Visitor); +float_helper!(f64, deserialize_f64, F64Visitor); -- 2.39.5 _______________________________________________ pbs-devel mailing list pbs-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel