gled-model 2.30.0

The data model of gled: projects, scenes, effects, animations, curves and assets
Documentation
//! Maps with numeric keys. JSON asset files store the keys as strings,
//! MessagePack messages as numbers; both load. Keys that are no valid number
//! are dropped, as older files may contain them.

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

/// A numeric map key written as a number or as a string.
struct IndexKey(Option<u64>);

impl<'de> Deserialize<'de> for IndexKey {
    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        struct IndexKeyVisitor;

        impl Visitor<'_> for IndexKeyVisitor {
            type Value = IndexKey;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("a number or a string holding one")
            }

            fn visit_u64<E: de::Error>(self, value: u64) -> Result<IndexKey, E> {
                Ok(IndexKey(Some(value)))
            }

            fn visit_i64<E: de::Error>(self, value: i64) -> Result<IndexKey, E> {
                Ok(IndexKey(u64::try_from(value).ok()))
            }

            fn visit_str<E: de::Error>(self, value: &str) -> Result<IndexKey, E> {
                Ok(IndexKey(value.parse().ok()))
            }
        }

        deserializer.deserialize_any(IndexKeyVisitor)
    }
}

struct IndexMapVisitor<K, T, M>(PhantomData<(K, T, M)>);

impl<'de, K, T, M> Visitor<'de> for IndexMapVisitor<K, T, M>
where
    K: TryFrom<u64>,
    T: Deserialize<'de>,
    M: FromIterator<(K, T)>,
{
    type Value = M;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("a map with numeric keys")
    }

    fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<M, A::Error> {
        let mut entries = Vec::with_capacity(map.size_hint().unwrap_or(0));
        while let Some((key, value)) = map.next_entry::<IndexKey, T>()? {
            if let Some(key) = key.0.and_then(|key| K::try_from(key).ok()) {
                entries.push((key, value));
            }
        }
        Ok(entries.into_iter().collect())
    }
}

fn deserialize_index_map<'de, D, K, T, M>(deserializer: D) -> Result<M, D::Error>
where
    D: Deserializer<'de>,
    K: TryFrom<u64>,
    T: Deserialize<'de>,
    M: FromIterator<(K, T)>,
{
    deserializer.deserialize_map(IndexMapVisitor(PhantomData))
}

pub(crate) fn deserialize_usize_index_btreemap<'de, D, T: DeserializeOwned>(
    deserializer: D,
) -> Result<BTreeMap<usize, T>, D::Error>
where
    D: Deserializer<'de>,
{
    deserialize_index_map(deserializer)
}

pub(crate) fn deserialize_u16_index_btreemap<'de, D, T: DeserializeOwned>(
    deserializer: D,
) -> Result<BTreeMap<u16, T>, D::Error>
where
    D: Deserializer<'de>,
{
    deserialize_index_map(deserializer)
}

pub(crate) fn deserialize_u16_index_hashmap<'de, D, T: DeserializeOwned>(
    deserializer: D,
) -> Result<HashMap<u16, T>, D::Error>
where
    D: Deserializer<'de>,
{
    deserialize_index_map::<D, u16, T, HashMap<u16, T>>(deserializer)
}

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

    #[derive(Debug, PartialEq, serde::Deserialize)]
    struct Indexed {
        #[serde(deserialize_with = "deserialize_u16_index_btreemap")]
        map: BTreeMap<u16, String>,
    }

    #[test]
    fn string_keys_load_and_invalid_ones_are_dropped() {
        let indexed: Indexed =
            serde_json::from_str(r#"{"map": {"1": "one", "x": "dropped", "70000": "too big"}}"#)
                .expect("loads");
        assert_eq!(indexed.map, BTreeMap::from([(1, "one".to_owned())]));
    }
}