diff options
author | Tomasz Kramkowski <tomasz@kramkow.ski> | 2025-07-19 21:05:49 +0100 |
---|---|---|
committer | Tomasz Kramkowski <tomasz@kramkow.ski> | 2025-07-19 21:05:49 +0100 |
commit | 5e96240fdfedd391c164dacf8b87d629f4582463 (patch) | |
tree | b9098aa9c4abca27b9e9d51dca1e71a046c1ac80 /src/config | |
parent | f0f7b4910d37331dba69528c666e5e3168dccc84 (diff) | |
download | mqttr-5e96240fdfedd391c164dacf8b87d629f4582463.tar.gz mqttr-5e96240fdfedd391c164dacf8b87d629f4582463.tar.xz mqttr-5e96240fdfedd391c164dacf8b87d629f4582463.zip |
Move serde helpers to a separate module
Diffstat (limited to 'src/config')
-rw-r--r-- | src/config/helpers.rs | 193 |
1 files changed, 193 insertions, 0 deletions
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 <tomasz@kramkow.ski> +// 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<Option<QoS>, 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<Duration, D::Error> +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<E>(self, v: i64) -> Result<Self::Value, E> + 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<E>(self, v: f64) -> Result<Self::Value, E> + 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<Option<Duration>, 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<D>(deserializer: D) -> Result<Self, D::Error> + 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<A>(self, seq: A) -> Result<Self::Value, A::Error> + 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<A>(self, map: A) -> Result<Self::Value, A::Error> + where + A: serde::de::MapAccess<'de>, + { + #[derive(Deserialize)] + struct Helper { + command: Box<[String]>, + #[serde(default, deserialize_with = "deserialize_timeout_opt")] + timeout: Option<Duration>, + } + + 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<D>(deserializer: D) -> Result<Self, D::Error> + 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<A>(self, seq: A) -> Result<Self::Value, A::Error> + 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<A>(self, map: A) -> Result<Self::Value, A::Error> + where + A: serde::de::MapAccess<'de>, + { + #[derive(Deserialize)] + struct Helper { + programs: Box<[super::Program]>, + #[serde(default, deserialize_with = "deserialize_qos_opt")] + qos: Option<QoS>, + } + + let helper: Helper = + Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))?; + Ok(super::Route { + programs: helper.programs, + qos: helper.qos, + }) + } + } + + deserializer.deserialize_any(VecOrRoute) + } +} |