midiserde 0.1.1

When mini isn't enough and serde is too much
Documentation
//! Midiserde — when mini isn't enough and serde is too much.
//!
//! A companion to [miniserde](https://github.com/dtolnay/miniserde) that adds
//! the attributes and patterns people keep asking for.
//!
//! # Features
//!
//! - **macros** (default: on) — Enables derive macros `Deserialize` and `Serialize`,
//!   re-exported here for convenience. Use `midiserde = { version = "0.1" }`.

// Traits only; macros come from midi-internal to avoid conflict with miniserde's derive macros.
pub use miniserde::de::Deserialize;
pub use miniserde::ser::Serialize;
pub use miniserde::json;

mod value;
pub use value::{from_value, replay_value_to_visitor, to_value};

#[cfg(feature = "macros")]
pub use midi_internal::{Deserialize, Serialize};

pub mod with;

// =============================================================================
// Flatten support traits
// =============================================================================

/// A `de::Map` that produces a typed `T` directly after being driven.
///
/// Each struct that derives `midiserde::Deserialize` automatically implements
/// this trait, enabling it to be used as a `#[mini(flatten)]` field in a parent.
///
/// `accepts_key` lets the parent route JSON keys at parse time instead of
/// buffering them for later replay.
pub trait FlattenMap<T>: miniserde::de::Map {
    /// Returns `true` if this map handles the given JSON key.
    fn accepts_key(&self, k: &str) -> bool;

    /// Produce the deserialized value. Call after `finish()`.
    fn build(&mut self) -> miniserde::Result<T>;
}

/// Types whose fields can be flattened into a parent during deserialization.
///
/// Implemented by:
/// - Structs that derive `Deserialize` (struct flatten; `accepts_key` routes known fields)
/// - `HashMap<String, V>` and `BTreeMap<String, V>` (map flatten; capture unknown keys; `accepts_key` always true)
///
/// Returns `Box<dyn FlattenMap<Self>>` — one heap allocation for the builder.
pub trait FlattenDeserialize: Sized {
    fn create_flatten_map() -> Box<dyn FlattenMap<Self>>;
}

/// Produces a `ser::Map` over this type's fields for flatten serialization.
///
/// Enables a parent to inline this type's key-value pairs instead of nesting
/// them in a sub-object, without going through an intermediate `Value`.
pub trait SerializeMapBuilder {
    fn create_ser_map(&self) -> Box<dyn miniserde::ser::Map + '_>;
}

// HashMap and BTreeMap already implement miniserde::Serialize with streaming
// Fragment::Map; we expose that as SerializeMapBuilder so map flatten can
// stream directly instead of using to_value + buffer.

impl<K, V, H> SerializeMapBuilder for std::collections::HashMap<K, V, H>
where
    K: std::hash::Hash + Eq + ToString,
    V: miniserde::Serialize,
    H: std::hash::BuildHasher,
{
    fn create_ser_map(&self) -> Box<dyn miniserde::ser::Map + '_> {
        match miniserde::Serialize::begin(self) {
            miniserde::ser::Fragment::Map(m) => m,
            _ => unreachable!("HashMap::begin returns Fragment::Map"),
        }
    }
}

impl<K, V> SerializeMapBuilder for std::collections::BTreeMap<K, V>
where
    K: ToString,
    V: miniserde::Serialize,
{
    fn create_ser_map(&self) -> Box<dyn miniserde::ser::Map + '_> {
        match miniserde::Serialize::begin(self) {
            miniserde::ser::Fragment::Map(m) => m,
            _ => unreachable!("BTreeMap::begin returns Fragment::Map"),
        }
    }
}

// =============================================================================
// FlattenMap / FlattenDeserialize for HashMap and BTreeMap (capture unknown keys)
// =============================================================================
//
// Maps accept any key, so `accepts_key` always returns true. This lets the derive
// use the same FlattenMap machinery as struct flatten, simplifying generated code.

struct HashMapFlattenMap<V> {
    buffer: std::vec::Vec<(std::string::String, miniserde::json::Value)>,
    current_key: Option<std::string::String>,
    current_value: Option<miniserde::json::Value>,
    _marker: std::marker::PhantomData<V>,
}

impl<V> HashMapFlattenMap<V> {
    fn new() -> Self {
        Self {
            buffer: std::vec::Vec::new(),
            current_key: None,
            current_value: None,
            _marker: std::marker::PhantomData,
        }
    }
    fn shift(&mut self) {
        if let (Some(k), Some(v)) = (self.current_key.take(), self.current_value.take()) {
            self.buffer.push((k, v));
        }
    }
}

impl<V> miniserde::de::Map for HashMapFlattenMap<V>
where
    V: miniserde::de::Deserialize,
{
    fn key(&mut self, k: &str) -> miniserde::Result<&mut dyn miniserde::de::Visitor> {
        self.shift();
        self.current_key = Some(k.to_string());
        Ok(miniserde::Deserialize::begin(&mut self.current_value))
    }

    fn finish(&mut self) -> miniserde::Result<()> {
        self.shift();
        Ok(())
    }
}

impl<V, H> FlattenMap<std::collections::HashMap<std::string::String, V, H>>
    for HashMapFlattenMap<V>
where
    V: miniserde::de::Deserialize + 'static,
    H: std::hash::BuildHasher + Default,
{
    fn accepts_key(&self, _k: &str) -> bool {
        true
    }

    fn build(&mut self) -> miniserde::Result<std::collections::HashMap<std::string::String, V, H>> {
        let obj = miniserde::json::Object::from_iter(std::mem::take(&mut self.buffer));
        from_value(&miniserde::json::Value::Object(obj))
    }
}

impl<V, H> FlattenDeserialize for std::collections::HashMap<std::string::String, V, H>
where
    V: miniserde::de::Deserialize + 'static,
    H: std::hash::BuildHasher + Default,
{
    fn create_flatten_map(
    ) -> Box<dyn FlattenMap<std::collections::HashMap<std::string::String, V, H>>> {
        Box::new(HashMapFlattenMap::<V>::new())
    }
}

struct BTreeMapFlattenMap<V> {
    buffer: std::vec::Vec<(std::string::String, miniserde::json::Value)>,
    current_key: Option<std::string::String>,
    current_value: Option<miniserde::json::Value>,
    _marker: std::marker::PhantomData<V>,
}

impl<V> BTreeMapFlattenMap<V> {
    fn new() -> Self {
        Self {
            buffer: std::vec::Vec::new(),
            current_key: None,
            current_value: None,
            _marker: std::marker::PhantomData,
        }
    }
    fn shift(&mut self) {
        if let (Some(k), Some(v)) = (self.current_key.take(), self.current_value.take()) {
            self.buffer.push((k, v));
        }
    }
}

impl<V> miniserde::de::Map for BTreeMapFlattenMap<V>
where
    V: miniserde::de::Deserialize,
{
    fn key(&mut self, k: &str) -> miniserde::Result<&mut dyn miniserde::de::Visitor> {
        self.shift();
        self.current_key = Some(k.to_string());
        Ok(miniserde::Deserialize::begin(&mut self.current_value))
    }

    fn finish(&mut self) -> miniserde::Result<()> {
        self.shift();
        Ok(())
    }
}

impl<V> FlattenMap<std::collections::BTreeMap<std::string::String, V>>
    for BTreeMapFlattenMap<V>
where
    V: miniserde::de::Deserialize + 'static,
{
    fn accepts_key(&self, _k: &str) -> bool {
        true
    }

    fn build(&mut self) -> miniserde::Result<std::collections::BTreeMap<std::string::String, V>> {
        let obj = miniserde::json::Object::from_iter(std::mem::take(&mut self.buffer));
        from_value(&miniserde::json::Value::Object(obj))
    }
}

impl<V> FlattenDeserialize for std::collections::BTreeMap<std::string::String, V>
where
    V: miniserde::de::Deserialize + 'static,
{
    fn create_flatten_map() -> Box<dyn FlattenMap<std::collections::BTreeMap<std::string::String, V>>> {
        Box::new(BTreeMapFlattenMap::<V>::new())
    }
}