aboutsummaryrefslogtreecommitdiffstats
path: root/src/config
diff options
context:
space:
mode:
Diffstat (limited to 'src/config')
-rw-r--r--src/config/helpers.rs193
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)
+ }
+}