From 5e96240fdfedd391c164dacf8b87d629f4582463 Mon Sep 17 00:00:00 2001 From: Tomasz Kramkowski Date: Sat, 19 Jul 2025 21:05:49 +0100 Subject: Move serde helpers to a separate module --- src/config/helpers.rs | 193 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 src/config/helpers.rs (limited to 'src/config/helpers.rs') diff --git a/src/config/helpers.rs b/src/config/helpers.rs new file mode 100644 index 0000000..c03e6fd --- /dev/null +++ b/src/config/helpers.rs @@ -0,0 +1,193 @@ +// SPDX-FileCopyrightText: 2025 Tomasz Kramkowski +// SPDX-License-Identifier: GPL-3.0-or-later + +use std::{fmt, time::Duration}; + +use rumqttc::QoS; +use serde::{ + de::{self, Visitor}, + Deserialize, Deserializer, +}; + +#[allow(clippy::enum_variant_names)] +#[derive(Deserialize, Debug)] +#[serde(remote = "QoS", rename_all = "kebab-case")] +#[repr(u8)] +pub enum QoSDef { + AtMostOnce = 0, + AtLeastOnce = 1, + ExactlyOnce = 2, +} + +pub fn deserialize_qos_opt<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + struct Helper(#[serde(with = "QoSDef")] QoS); + + let helper = Option::deserialize(deserializer)?; + Ok(helper.map(|Helper(external)| external)) +} + +pub fn deserialize_timeout<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + struct DurationVisitor; + + impl<'de> de::Visitor<'de> for DurationVisitor { + type Value = Duration; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a positive number") + } + + fn visit_i64(self, v: i64) -> Result + where + E: de::Error, + { + if v < 0 { + return Err(de::Error::invalid_value( + de::Unexpected::Signed(v), + &"a non-negative number", + )); + } + if v == 0 { + Ok(Duration::MAX) + } else { + Ok(Duration::from_secs(v as u64)) + } + } + + fn visit_f64(self, v: f64) -> Result + where + E: de::Error, + { + if v < 0.0 { + return Err(de::Error::invalid_value( + de::Unexpected::Float(v), + &"a non-negative number", + )); + } + if v == 0.0 { + Ok(Duration::MAX) + } else { + Ok(Duration::from_secs_f64(v)) + } + } + } + + deserializer.deserialize_any(DurationVisitor) +} + +pub fn deserialize_timeout_opt<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + struct Helper(#[serde(deserialize_with = "deserialize_timeout")] Duration); + + let helper = Option::deserialize(deserializer)?; + Ok(helper.map(|Helper(external)| external)) +} + +impl<'de> Deserialize<'de> for super::Program { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct VecOrProgram; + + impl<'de> Visitor<'de> for VecOrProgram { + type Value = super::Program; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("map or seq") + } + + fn visit_seq(self, seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let vec: Box<[String]> = + Deserialize::deserialize(de::value::SeqAccessDeserializer::new(seq))?; + Ok(super::Program { + command: vec, + timeout: None, + }) + } + + fn visit_map(self, map: A) -> Result + where + A: serde::de::MapAccess<'de>, + { + #[derive(Deserialize)] + struct Helper { + command: Box<[String]>, + #[serde(default, deserialize_with = "deserialize_timeout_opt")] + timeout: Option, + } + + let helper: Helper = + Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))?; + Ok(super::Program { + command: helper.command, + timeout: helper.timeout, + }) + } + } + + deserializer.deserialize_any(VecOrProgram) + } +} + +impl<'de> Deserialize<'de> for super::Route { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct VecOrRoute; + + impl<'de> Visitor<'de> for VecOrRoute { + type Value = super::Route; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("map or seq") + } + + fn visit_seq(self, seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let vec: Box<[super::Program]> = + Deserialize::deserialize(de::value::SeqAccessDeserializer::new(seq))?; + Ok(super::Route { + programs: vec, + qos: None, + }) + } + + fn visit_map(self, map: A) -> Result + where + A: serde::de::MapAccess<'de>, + { + #[derive(Deserialize)] + struct Helper { + programs: Box<[super::Program]>, + #[serde(default, deserialize_with = "deserialize_qos_opt")] + qos: Option, + } + + let helper: Helper = + Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))?; + Ok(super::Route { + programs: helper.programs, + qos: helper.qos, + }) + } + } + + deserializer.deserialize_any(VecOrRoute) + } +} -- cgit v1.2.3-70-g09d2