// 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 { 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 { 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) } }