diff --git a/src/automation.rs b/src/automation.rs index 2088a8e0..f9278a17 100644 --- a/src/automation.rs +++ b/src/automation.rs @@ -4,6 +4,9 @@ use std::{ time::{Duration, Instant}, }; +use serde::de::{self, MapAccess, SeqAccess, Visitor}; +use serde::Deserializer; +use serde::Serializer; use serde::{Deserialize, Serialize}; use thiserror::Error; use uuid::Uuid; @@ -128,9 +131,17 @@ pub struct InspectNode { pub name: String, pub class_name: String, pub path: String, - #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + #[serde( + default, + with = "string_map_or_empty_array", + skip_serializing_if = "BTreeMap::is_empty" + )] pub properties: BTreeMap, - #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + #[serde( + default, + with = "string_map_or_empty_array", + skip_serializing_if = "BTreeMap::is_empty" + )] pub attributes: BTreeMap, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub tags: Vec, @@ -533,6 +544,61 @@ fn enforce_terminal_limit(inner: &mut AutomationJobStoreInner) { } } +mod string_map_or_empty_array { + use super::*; + use std::fmt; + + pub fn serialize( + value: &BTreeMap, + serializer: S, + ) -> Result + where + S: Serializer, + { + BTreeMap::serialize(value, serializer) + } + + pub fn deserialize<'de, D>( + deserializer: D, + ) -> Result, D::Error> + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(MapVisitor) + } + + struct MapVisitor; + + impl<'de> Visitor<'de> for MapVisitor { + type Value = BTreeMap; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a map, or an empty array") + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + let mut output = BTreeMap::new(); + while let Some((key, value)) = map.next_entry::()? { + output.insert(key, value); + } + Ok(output) + } + + fn visit_seq(self, mut sequence: A) -> Result + where + A: SeqAccess<'de>, + { + if sequence.next_element::()?.is_some() { + return Err(de::Error::invalid_length(1, &"an empty array")); + } + Ok(BTreeMap::new()) + } + } +} + #[cfg(test)] mod tests { use super::*;