recallable 0.2.0

Traits (`Recallable`, `Recall`, `TryRecall`) and macros for defining Memento pattern types and their state restoration behaviors.
Documentation
#![cfg(feature = "serde")]

use recallable::{Recall, Recallable};
use serde::{Deserialize, de::DeserializeOwned};

#[derive(Clone, Debug, PartialEq, Deserialize, recallable::Recallable, recallable::Recall)]
struct Counter {
    value: i32,
}

#[derive(Clone, Debug, PartialEq, Deserialize)]
struct ReplacingOption<T>(Option<T>);

impl<T> Recallable for ReplacingOption<T> {
    type Memento = Self;
}

impl<T> Recall for ReplacingOption<T> {
    fn recall(&mut self, memento: Self::Memento) {
        *self = memento;
    }
}

#[derive(Clone, Debug, PartialEq)]
struct SelectiveOption<T>(Option<T>);

impl<T: Recallable> Recallable for SelectiveOption<T> {
    type Memento = Option<T::Memento>;
}

impl<T: Recall> Recall for SelectiveOption<T> {
    fn recall(&mut self, memento: Self::Memento) {
        if let (Some(current), Some(next)) = (&mut self.0, memento) {
            current.recall(next);
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
struct ZippedVec<T>(Vec<T>);

impl<T: Recallable> Recallable for ZippedVec<T> {
    type Memento = Vec<T::Memento>;
}

impl<T: Recall> Recall for ZippedVec<T> {
    fn recall(&mut self, memento: Self::Memento) {
        for (current, next) in self.0.iter_mut().zip(memento) {
            current.recall(next);
        }
    }
}

impl<T> From<ZippedVec<T>> for Vec<T::Memento>
where
    T: Recallable,
    T::Memento: From<T>,
{
    fn from(value: ZippedVec<T>) -> Self {
        value.0.into_iter().map(Into::into).collect()
    }
}

#[derive(Clone, Debug, PartialEq, recallable::Recallable, recallable::Recall)]
struct ReplacingOptionOuter {
    #[recallable]
    inner: ReplacingOption<Counter>,
}

#[cfg(not(feature = "impl_from"))]
#[derive(Clone, Debug, PartialEq, recallable::Recallable, recallable::Recall)]
struct SelectiveOptionOuter {
    #[recallable]
    inner: SelectiveOption<Counter>,
}

#[derive(Clone, Debug, PartialEq, recallable::Recallable, recallable::Recall)]
struct ZippedVecOuter {
    #[recallable]
    inner: ZippedVec<Counter>,
}

trait IsReplacingOptionMemento {}

impl<T> IsReplacingOptionMemento for ReplacingOption<T> {}

trait IsOptionMemento {}

impl<T> IsOptionMemento for Option<T> {}

trait IsVecMemento {}

impl<T> IsVecMemento for Vec<T> {}

const fn counter(value: i32) -> Counter {
    Counter { value }
}

fn counter_memento(value: i32) -> <Counter as Recallable>::Memento {
    parse_memento::<Counter>(&format!(r#"{{"value":{value}}}"#))
}

fn parse_memento<T>(json: &str) -> <T as Recallable>::Memento
where
    T: Recallable,
    <T as Recallable>::Memento: DeserializeOwned,
{
    serde_json::from_str(json).unwrap()
}

#[test]
fn derived_outers_accept_multiple_container_memento_shapes() {
    fn assert_is_replacing_option_memento<T: IsReplacingOptionMemento>() {}
    fn assert_is_option_memento<T: IsOptionMemento>() {}
    fn assert_is_vec_memento<T: IsVecMemento>() {}

    assert_is_replacing_option_memento::<<ReplacingOption<Counter> as Recallable>::Memento>();
    assert_is_option_memento::<<SelectiveOption<Counter> as Recallable>::Memento>();
    assert_is_vec_memento::<<ZippedVec<Counter> as Recallable>::Memento>();

    let _: fn(&mut ReplacingOptionOuter, <ReplacingOptionOuter as Recallable>::Memento) =
        <ReplacingOptionOuter as Recall>::recall;
    #[cfg(not(feature = "impl_from"))]
    let _: fn(&mut SelectiveOptionOuter, <SelectiveOptionOuter as Recallable>::Memento) =
        <SelectiveOptionOuter as Recall>::recall;
    let _: fn(&mut ZippedVecOuter, <ZippedVecOuter as Recallable>::Memento) =
        <ZippedVecOuter as Recall>::recall;
}

#[test]
fn replacing_option_recall_replaces_the_whole_value() {
    let mut value = ReplacingOption(Some(counter(1)));
    value.recall(ReplacingOption(None));
    assert_eq!(value, ReplacingOption(None));
}

#[test]
fn selective_option_recall_updates_inner_value_when_both_sides_are_some() {
    let mut value = SelectiveOption(Some(counter(1)));
    value.recall(Some(counter_memento(9)));

    assert_eq!(value, SelectiveOption(Some(counter(9))));
}

#[test]
fn selective_option_recall_keeps_existing_some_when_memento_is_none() {
    let mut value = SelectiveOption(Some(counter(1)));
    value.recall(None);

    assert_eq!(value, SelectiveOption(Some(counter(1))));
}

#[test]
fn selective_option_recall_keeps_existing_none_when_memento_is_some() {
    let mut value = SelectiveOption::<Counter>(None);
    value.recall(Some(counter_memento(9)));

    assert_eq!(value, SelectiveOption(None));
}

#[test]
fn zipped_vec_recall_updates_only_the_shared_prefix_for_shorter_mementos() {
    let mut value = ZippedVec(vec![counter(1), counter(2), counter(3)]);
    value.recall(vec![counter_memento(10), counter_memento(20)]);

    assert_eq!(value, ZippedVec(vec![counter(10), counter(20), counter(3)]));
}

#[test]
fn zipped_vec_recall_ignores_extra_memento_elements_when_they_outnumber_values() {
    let mut value = ZippedVec(vec![counter(1), counter(2)]);
    value.recall(vec![
        counter_memento(10),
        counter_memento(20),
        counter_memento(30),
    ]);

    assert_eq!(value, ZippedVec(vec![counter(10), counter(20)]));
}