bo4e-edifact-types 0.1.26

Generic interchange model types for BO4E/EDIFACT conversion
Documentation
//! Serde helpers for map-keyed typed entities.

use serde::de::{self, DeserializeOwned, Deserializer, MapAccess, SeqAccess, Visitor};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fmt;
use std::marker::PhantomData;

/// Deserialize `Option<BTreeMap<K, V>>` from array, object, or null.
///
/// Array input: each element is a V; the key K is extracted by trying each
/// string field on the element as a potential K value (via serde deserialization).
///
/// Object input: standard BTreeMap deserialization.
pub fn deserialize_map_or_vec<'de, D, K, V>(
    deserializer: D,
) -> Result<Option<BTreeMap<K, V>>, D::Error>
where
    D: Deserializer<'de>,
    K: DeserializeOwned + Ord + Serialize,
    V: DeserializeOwned,
{
    struct MapOrVecVisitor<K, V>(PhantomData<(K, V)>);

    impl<'de, K, V> Visitor<'de> for MapOrVecVisitor<K, V>
    where
        K: DeserializeOwned + Ord + Serialize,
        V: DeserializeOwned,
    {
        type Value = Option<BTreeMap<K, V>>;

        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("a map, an array, or null")
        }

        fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
            Ok(None)
        }

        fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
            Ok(None)
        }

        fn visit_map<M: MapAccess<'de>>(self, map: M) -> Result<Self::Value, M::Error> {
            // Collect as JSON Value first to support both keyed-map and single-entity inputs.
            let val: serde_json::Value =
                Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))?;
            // Try standard BTreeMap deserialization (keys = qualifier codes like "Z04")
            if let Ok(btree) = serde_json::from_value::<BTreeMap<K, V>>(val.clone()) {
                return Ok(Some(btree));
            }
            // Fallback: treat as a single entity object and extract the key from its fields
            if let Some(key) = find_key_in_value::<K>(&val) {
                let entity: V = serde_json::from_value(val).map_err(de::Error::custom)?;
                let mut map = BTreeMap::new();
                map.insert(key, entity);
                return Ok(Some(map));
            }
            Err(de::Error::custom(
                "map value is neither a keyed BTreeMap nor a single entity with a valid key field",
            ))
        }

        fn visit_seq<S: SeqAccess<'de>>(self, mut seq: S) -> Result<Self::Value, S::Error> {
            let mut map = BTreeMap::new();
            while let Some(val) = seq.next_element::<serde_json::Value>()? {
                let key = find_key_in_value::<K>(&val)
                    .ok_or_else(|| de::Error::custom("array element has no valid map key field"))?;
                let entity: V = serde_json::from_value(val).map_err(de::Error::custom)?;
                map.insert(key, entity);
            }
            Ok(Some(map))
        }
    }

    deserializer.deserialize_any(MapOrVecVisitor(PhantomData))
}

/// Deserialize `Option<Vec<T>>` from array, single object, or null.
///
/// The dynamic forward mapping produces single objects for single-rep entities
/// and arrays for multi-rep entities. Typed structs always use `Vec<T>`.
/// This helper accepts both formats.
pub fn deserialize_vec_or_single<'de, D, T>(deserializer: D) -> Result<Option<Vec<T>>, D::Error>
where
    D: Deserializer<'de>,
    T: DeserializeOwned,
{
    struct VecOrSingleVisitor<T>(PhantomData<T>);

    impl<'de, T: DeserializeOwned> Visitor<'de> for VecOrSingleVisitor<T> {
        type Value = Option<Vec<T>>;

        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("an array, an object, or null")
        }

        fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
            Ok(None)
        }

        fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
            Ok(None)
        }

        fn visit_seq<S: SeqAccess<'de>>(self, seq: S) -> Result<Self::Value, S::Error> {
            let vec: Vec<T> =
                Deserialize::deserialize(de::value::SeqAccessDeserializer::new(seq))?;
            Ok(Some(vec))
        }

        fn visit_map<M: MapAccess<'de>>(self, map: M) -> Result<Self::Value, M::Error> {
            let single: T =
                Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))?;
            Ok(Some(vec![single]))
        }
    }

    deserializer.deserialize_any(VecOrSingleVisitor(PhantomData))
}

/// Try to find a key of type K in a JSON value by checking all string fields.
/// Also checks one level of nested objects (for companion fields stored in sub-objects).
fn find_key_in_value<K: for<'de> Deserialize<'de>>(val: &serde_json::Value) -> Option<K> {
    let obj = val.as_object()?;
    // Check top-level string fields
    for (_field_name, field_val) in obj {
        if let Some(s) = field_val.as_str() {
            if let Ok(key) = serde_json::from_value::<K>(serde_json::Value::String(s.to_string())) {
                return Some(key);
            }
        }
    }
    // Check one level of nested objects (companion types like geschaeftspartnerEdifact)
    for (_field_name, field_val) in obj {
        if let Some(nested_obj) = field_val.as_object() {
            for (_nested_name, nested_val) in nested_obj {
                if let Some(s) = nested_val.as_str() {
                    if let Ok(key) =
                        serde_json::from_value::<K>(serde_json::Value::String(s.to_string()))
                    {
                        return Some(key);
                    }
                }
            }
        }
    }
    None
}

#[cfg(test)]
mod tests {
    use super::*;

    #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
    enum TestKey {
        #[serde(rename = "MS")]
        Ms,
        #[serde(rename = "MR")]
        Mr,
    }

    #[derive(Debug, Clone, Default, Serialize, Deserialize)]
    #[serde(rename_all = "camelCase")]
    struct TestEntity {
        pub marktrolle: Option<String>,
        pub name: Option<String>,
    }

    #[derive(Debug, Clone, Default, Serialize, Deserialize)]
    struct TestContainer {
        #[serde(default, deserialize_with = "deserialize_map_or_vec")]
        pub items: Option<BTreeMap<TestKey, TestEntity>>,
    }

    #[test]
    fn test_deserialize_from_array() {
        let json = r#"{"items": [
            {"marktrolle": "MS", "name": "Sender"},
            {"marktrolle": "MR", "name": "Recipient"}
        ]}"#;
        let container: TestContainer = serde_json::from_str(json).unwrap();
        let items = container.items.unwrap();
        assert_eq!(items.len(), 2);
        assert_eq!(items[&TestKey::Ms].name.as_deref(), Some("Sender"));
        assert_eq!(items[&TestKey::Mr].name.as_deref(), Some("Recipient"));
    }

    #[test]
    fn test_deserialize_from_map() {
        let json = r#"{"items": {
            "MS": {"name": "Sender"},
            "MR": {"name": "Recipient"}
        }}"#;
        let container: TestContainer = serde_json::from_str(json).unwrap();
        let items = container.items.unwrap();
        assert_eq!(items.len(), 2);
        assert_eq!(items[&TestKey::Ms].name.as_deref(), Some("Sender"));
    }

    #[test]
    fn test_deserialize_null() {
        let json = r#"{"items": null}"#;
        let container: TestContainer = serde_json::from_str(json).unwrap();
        assert!(container.items.is_none());
    }

    #[test]
    fn test_deserialize_missing_field() {
        let json = r#"{}"#;
        let container: TestContainer = serde_json::from_str(json).unwrap();
        assert!(container.items.is_none());
    }

    #[test]
    fn test_serialize_as_map() {
        let mut items = BTreeMap::new();
        items.insert(
            TestKey::Ms,
            TestEntity {
                marktrolle: Some("MS".into()),
                name: Some("Sender".into()),
            },
        );
        let container = TestContainer { items: Some(items) };
        let json = serde_json::to_value(&container).unwrap();
        assert!(json["items"].is_object());
        assert!(json["items"]["MS"].is_object());
    }
}