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 | |
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')
-rw-r--r-- | src/config.rs | 194 | ||||
-rw-r--r-- | src/config/helpers.rs | 193 |
2 files changed, 199 insertions, 188 deletions
diff --git a/src/config.rs b/src/config.rs index b04b72c..d3fc75b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,21 +1,22 @@ // SPDX-FileCopyrightText: 2025 Tomasz Kramkowski <tomasz@kramkow.ski> // SPDX-License-Identifier: GPL-3.0-or-later +mod helpers; + use std::{ - collections::HashMap, fmt, fs::File, io::Read, os::unix::fs::PermissionsExt, path::Path, + collections::HashMap, fs::File, io::Read, os::unix::fs::PermissionsExt, path::Path, time::Duration, }; use anyhow::bail; use log::LevelFilter; use rumqttc::{AsyncClient, EventLoop, MqttOptions, QoS}; -use serde::{ - de::{self, Visitor}, - Deserialize, Deserializer, -}; +use serde::Deserialize; use crate::PROGRAM; +use helpers::*; + #[derive(Deserialize, Debug, PartialEq)] pub struct Logging { #[serde(default = "default_level_filter")] @@ -63,27 +64,6 @@ fn default_level_filter() -> LevelFilter { LevelFilter::Info } -#[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)) -} - #[derive(Debug, PartialEq, Clone)] pub struct Program { // TODO: Figure out a way to allow arbitrary unix paths (arbitrary @@ -92,174 +72,12 @@ pub struct Program { pub timeout: Option<Duration>, } -impl<'de> Deserialize<'de> for Program { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: Deserializer<'de>, - { - struct VecOrProgram; - - impl<'de> Visitor<'de> for VecOrProgram { - type Value = 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(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(Program { - command: helper.command, - timeout: helper.timeout, - }) - } - } - - deserializer.deserialize_any(VecOrProgram) - } -} - #[derive(Debug)] pub struct Route { pub programs: Box<[Program]>, pub qos: Option<QoS>, } -impl<'de> Deserialize<'de> for Route { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: Deserializer<'de>, - { - struct VecOrRoute; - - impl<'de> Visitor<'de> for VecOrRoute { - type Value = 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<[Program]> = - Deserialize::deserialize(de::value::SeqAccessDeserializer::new(seq))?; - Ok(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<[Program]>, - #[serde(default, deserialize_with = "deserialize_qos_opt")] - qos: Option<QoS>, - } - - let helper: Helper = - Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))?; - Ok(Route { - programs: helper.programs, - qos: helper.qos, - }) - } - } - - deserializer.deserialize_any(VecOrRoute) - } -} - -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)) -} - #[derive(Deserialize, Debug)] pub struct Config { #[serde(default = "default_host")] 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) + } +} |