recallable 0.2.0

Traits (`Recallable`, `Recall`, `TryRecall`) and macros for defining Memento pattern types and their state restoration behaviors.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
//! # Recallable
//!
//! A crate for handling partial updates to data structures.
//!
//! This crate provides the [`Recallable`], [`Recall`], and [`TryRecall`] traits, along with
//! derive macros for `Recallable` and `Recall`, and an attribute macro `recallable_model`
//! re-exported from `recallable_macro` for easy derivation.
//!
//! The crate itself is `#![no_std]`. Enabling the `serde` feature does not require `std`; it only
//! turns on macro-generated serde support, and it remains usable in `no_std` environments when
//! your serde stack is configured without `std`.
//!
//! ## Motivation
//!
//! Many systems receive incremental updates where only a subset of fields change or can be
//! considered part of the state. This crate formalizes this pattern by defining a memento type for
//! a structure and providing a consistent way to apply such mementos safely.
//!
//! The crate intentionally does not prescribe one canonical memento shape for container-like
//! field types. A type may choose whole-value replacement, selective inner updates, or some other
//! domain-specific behavior, and the derive macros defer to that type's own
//! [`Recallable::Memento`] and [`Recall::recall`] implementations.

// Re-export the derive macros.
#![no_std]

extern crate self as recallable;

/// Attribute macro that prepares a struct for the Memento pattern.
///
/// Adds `#[derive(Recallable, Recall)]` automatically. When the `serde` feature is enabled,
/// also derives `serde::Serialize` on the struct and injects `#[serde(skip)]` on fields
/// marked with `#[recallable(skip)]`.
///
/// Lifetime parameters are supported only when the generated memento can stay owned:
/// non-skipped fields may not borrow one of the struct's lifetimes. Skipped borrowed
/// fields and lifetime-only markers such as `PhantomData<&'a T>` are allowed.
///
/// This example requires the `serde` feature.
///
/// ```rust
/// # #[cfg(feature = "serde")]
/// # {
/// use recallable::{Recall, Recallable, recallable_model};
///
/// #[recallable_model]
/// #[derive(Clone, Debug)]
/// struct Settings {
///     volume: u8,
///     brightness: u8,
///     #[recallable(skip)]
///     on_change: fn(),
/// }
///
/// fn noop() {}
///
/// let mut settings = Settings { volume: 50, brightness: 80, on_change: noop };
/// let memento: <Settings as Recallable>::Memento =
///     serde_json::from_str(r#"{"volume":75,"brightness":60}"#).unwrap();
/// settings.recall(memento);
/// assert_eq!(settings.volume, 75);
/// assert_eq!(settings.brightness, 60);
/// // on_change is skipped — unchanged by recall
/// # }
/// ```
pub use recallable_macro::recallable_model;

/// Derive macro that generates a companion memento struct and the [`Recallable`] trait impl.
///
/// The memento struct mirrors the original but replaces `#[recallable]`-annotated fields
/// with their `<FieldType as Recallable>::Memento` type and omits `#[recallable(skip)]` fields.
/// The generated companion type has the same visibility as the input struct.
/// Its fields are always emitted without visibility modifiers, so they remain private to the
/// containing module. This is intentional: mementos are meant to be created and consumed alongside
/// the companion struct, primarily via [`Recall::recall`] and [`TryRecall::try_recall`], with only
/// occasional same-file testing or debugging use.
/// For container-like field types, this is whatever memento shape that field type chose; the macro
/// does not special-case merge semantics.
/// When the `impl_from` feature is enabled, `#[derive(Recallable)]` also generates
/// `From<Struct>` for the memento type, which requires
/// `<FieldType as Recallable>::Memento: From<FieldType>` for each `#[recallable]` field.
///
/// Lifetime parameters are supported only when the generated memento can stay owned:
/// non-skipped fields may not borrow one of the struct's lifetimes. Skipped borrowed
/// fields and lifetime-only markers such as `PhantomData<&'a T>` are allowed.
///
/// This example requires the `serde` feature.
///
/// ```rust
/// # #[cfg(feature = "serde")]
/// # {
/// use recallable::{Recall, Recallable};
///
/// #[derive(Clone, Debug, serde::Serialize, Recallable, Recall)]
/// struct Outer {
///     label: String,
///     #[recallable]
///     inner: Inner,
/// }
///
/// #[derive(Clone, Debug, serde::Serialize, Recallable, Recall)]
/// struct Inner {
///     count: u32,
/// }
///
/// // The memento type is accessible via the associated type
/// let memento: <Outer as Recallable>::Memento =
///     serde_json::from_str(r#"{"label":"updated","inner":{"count":99}}"#).unwrap();
///
/// let mut outer = Outer { label: "original".into(), inner: Inner { count: 0 } };
/// outer.recall(memento);
/// assert_eq!(outer.label, "updated");
/// assert_eq!(outer.inner.count, 99);
/// # }
/// ```
pub use recallable_macro::Recallable;

/// Derive macro that generates the [`Recall`] trait implementation.
///
/// For plain fields, `recall` assigns the memento value directly. For fields annotated
/// with `#[recallable]`, it recursively calls `recall` on the nested value.
/// Fields marked `#[recallable(skip)]` are left untouched.
/// For `#[recallable]` fields, replace/merge behavior comes from the field type's own
/// [`Recall`] implementation.
///
/// Lifetime parameters are supported only when the generated memento can stay owned:
/// non-skipped fields may not borrow one of the struct's lifetimes. Skipped borrowed
/// fields and lifetime-only markers such as `PhantomData<&'a T>` are allowed.
///
/// This example requires the `serde` feature.
///
/// ```rust
/// # #[cfg(feature = "serde")]
/// # {
/// use recallable::{Recall, Recallable};
///
/// #[derive(Clone, Debug, serde::Serialize, Recallable, Recall)]
/// struct State {
///     score: i32,
///     #[recallable(skip)]
///     cached_label: String,
/// }
///
/// let mut state = State { score: 0, cached_label: "stale".into() };
/// let memento: <State as Recallable>::Memento =
///     serde_json::from_str(r#"{"score":42}"#).unwrap();
/// state.recall(memento);
/// assert_eq!(state.score, 42);
/// assert_eq!(state.cached_label, "stale"); // skip preserves the value
/// # }
/// ```
pub use recallable_macro::Recall;

/// A type that declares a companion memento type.
///
/// This trait intentionally does not prescribe one canonical memento shape for container-like
/// types. For example, one `Option`-like wrapper may choose `Self` as its memento while another
/// may choose `Option<T::Memento>` and perform selective inner updates.
///
/// ## Usage
///
/// ```rust
/// use recallable::{Recall, Recallable};
/// use serde::{Deserialize, Serialize};
///
/// #[derive(Debug, Serialize)]
/// pub struct Accumulator<T> {
///     prev_control_signal: T,
///     #[serde(skip)]
///     filter: fn(&i32) -> bool,
///     accumulated: u32,
/// }
///
/// //vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
/// // If we derive `Recallable` and `Recall` for `Accumulator`, the equivalent companion memento
/// // type and the `Recallable`/`Recall` implementations can be generated automatically.
/// // The generated companion type is exposed as
/// // `<Accumulator<T> as Recallable>::Memento`; its concrete struct name is an implementation
/// // detail of the derive.
/// //
/// // When deriving `Recallable`, a `From<Accumulator>` implementation is generated if the
/// // `impl_from` feature is enabled. For derived implementations, mark non-state fields with
/// // `#[recallable(skip)]` (and add `#[serde(skip)]` as needed when using serde).
/// // For `#[recallable]` fields, the derived `From` impl also requires
/// // `<FieldType as Recallable>::Memento: From<FieldType>`.
///
/// #[derive(PartialEq, Deserialize)]
/// pub struct AccumulatorMemento<T> {
///     prev_control_signal: T,
///     accumulated: u32,
/// }
///
/// impl<T> Recallable for Accumulator<T> {
///     type Memento = AccumulatorMemento<T>;
/// }
///
/// impl<T> From<Accumulator<T>> for AccumulatorMemento<T> {
///     fn from(acc: Accumulator<T>) -> Self {
///         Self {
///             prev_control_signal: acc.prev_control_signal,
///             accumulated: acc.accumulated,
///         }
///     }
/// }
///
/// impl<T> Recall for Accumulator<T> {
///     fn recall(&mut self, memento: Self::Memento) {
///         self.prev_control_signal = memento.prev_control_signal;
///         self.accumulated = memento.accumulated;
///     }
/// }
/// //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
///
/// fn main() {
///     let accumulator = Accumulator {
///         prev_control_signal: 6,
///         filter: |x: &i32| *x > 300,
///         accumulated: 15,
///     };
///
///     let state_bytes = postcard::to_vec::<_, 128>(&accumulator).unwrap();
///     let accumulator_memento: <Accumulator<i32> as Recallable>::Memento =
///         postcard::from_bytes(&state_bytes).unwrap();
///
///     let mut recovered_accumulator = Accumulator {
///         prev_control_signal: -1,
///         accumulated: 0,
///         ..accumulator
///     };
///
///     recovered_accumulator.recall(accumulator_memento);
///
///     assert_eq!(recovered_accumulator.prev_control_signal, accumulator.prev_control_signal);
///     assert_eq!(recovered_accumulator.accumulated, accumulator.accumulated);
/// }
/// ```
/// Declares the associated memento type.
pub trait Recallable {
    /// The type of memento associated with this structure.
    type Memento;
}

/// A type that can change state by absorbing one companion memento value.
///
/// The meaning of "apply this memento" is type-defined: a [`Recall`] implementation may replace
/// the whole value, merge fields, or selectively update nested state.
///
/// # Example
///
/// ```rust
/// use recallable::{Recall, Recallable};
///
/// struct Settings {
///     volume: u32,
///     brightness: u32,
/// }
///
/// #[derive(Clone, Debug, PartialEq)]
/// struct SettingsMemento {
///     volume: u32,
///     brightness: u32,
/// }
///
/// impl Recallable for Settings {
///     type Memento = SettingsMemento;
/// }
///
/// impl Recall for Settings {
///     fn recall(&mut self, memento: Self::Memento) {
///         self.volume = memento.volume;
///         self.brightness = memento.brightness;
///     }
/// }
///
/// fn main() {
///    let mut settings = Settings { volume: 50, brightness: 70 };
///    let memento = SettingsMemento { volume: 80, brightness: 40 };
///    settings.recall(memento);
///    assert_eq!(settings.volume, 80);
///    assert_eq!(settings.brightness, 40);
/// }
/// ```
pub trait Recall: Recallable {
    /// Applies the given memento to update the structure.
    fn recall(&mut self, memento: Self::Memento);
}

/// A fallible variant of [`Recall`].
///
/// This trait lets you apply a memento with validation and return a custom error
/// if it cannot be applied.
///
/// ## Usage
///
/// ```rust
/// use recallable::{TryRecall, Recallable};
/// use core::fmt;
///
/// #[derive(Debug)]
/// struct Config {
///     concurrency: u32,
/// }
///
/// #[derive(Clone, PartialEq)]
/// struct ConfigMemento {
///     concurrency: u32,
/// }
///
/// #[derive(Debug)]
/// struct RecallError(String);
///
/// impl fmt::Display for RecallError {
///     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
///         write!(f, "{}", self.0)
///     }
/// }
///
/// impl core::error::Error for RecallError {}
///
/// impl Recallable for Config {
///     type Memento = ConfigMemento;
/// }
///
/// impl From<Config> for ConfigMemento {
///     fn from(c: Config) -> Self {
///         Self { concurrency: c.concurrency }
///     }
/// }
///
/// impl TryRecall for Config {
///     type Error = RecallError;
///
///     fn try_recall(&mut self, memento: Self::Memento) -> Result<(), Self::Error> {
///         if memento.concurrency == 0 {
///             return Err(RecallError("Concurrency must be > 0".into()));
///         }
///         self.concurrency = memento.concurrency;
///         Ok(())
///     }
/// }
///
/// fn main() {
///     let mut config = Config { concurrency: 1 };
///     let valid_memento = ConfigMemento { concurrency: 4 };
///     config.try_recall(valid_memento).unwrap();
///     assert_eq!(config.concurrency, 4);
///
///     let invalid_memento = ConfigMemento { concurrency: 0 };
///     assert!(config.try_recall(invalid_memento).is_err());
/// }
/// ```
pub trait TryRecall: Recallable {
    /// The error type returned when applying a memento fails.
    type Error: core::error::Error + Send + Sync + 'static;

    /// Applies the provided recall to `self`.
    ///
    /// # Errors
    ///
    /// Returns an error if the memento is invalid or cannot be applied.
    #[must_use = "this returns a Result that may contain an error, which should be handled"]
    fn try_recall(&mut self, memento: Self::Memento) -> Result<(), Self::Error>;
}

/// Blanket implementation for all [`Recall`] types, where recalling is
/// infallible.
impl<T: Recall> TryRecall for T {
    type Error = core::convert::Infallible;

    #[inline(always)]
    fn try_recall(&mut self, memento: Self::Memento) -> Result<(), Self::Error> {
        self.recall(memento);
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    #[cfg(test)]
    extern crate std;

    use std::collections::HashMap;

    use super::{Recall, Recallable};

    // These are test-only smoke impls for path-shaped container fields. They intentionally use
    // whole-value replacement semantics; integration tests cover alternate container semantics.
    impl<T> Recallable for Option<T> {
        type Memento = Self;
    }

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

    impl<K, V, S> Recallable for HashMap<K, V, S> {
        type Memento = Self;
    }

    impl<K, V, S> Recall for HashMap<K, V, S> {
        fn recall(&mut self, memento: Self::Memento) {
            *self = memento;
        }
    }

    #[derive(Clone, Debug, PartialEq, crate::Recallable, crate::Recall)]
    struct GenericInner<T> {
        value: T,
    }

    #[derive(Clone, Debug, PartialEq, crate::Recallable, crate::Recall)]
    struct OptionOuter {
        #[recallable]
        value: Option<u32>,
    }

    #[derive(Clone, Debug, PartialEq, crate::Recallable, crate::Recall)]
    struct GenericOuter {
        #[recallable]
        value: GenericInner<u32>,
    }

    #[derive(Clone, Debug, PartialEq, crate::Recallable, crate::Recall)]
    struct HashMapOuter {
        #[recallable]
        value: HashMap<u8, u32>,
    }

    #[test]
    fn option_smoke_impl_replaces_value() {
        let mut value = Some(1u32);
        <Option<u32> as Recall>::recall(&mut value, None);
        assert_eq!(value, None);
    }

    #[test]
    fn hash_map_smoke_impl_replaces_value() {
        let mut value = HashMap::from([(1u8, 10u32)]);
        <HashMap<u8, u32> as Recall>::recall(&mut value, HashMap::from([(2u8, 20u32)]));
        assert_eq!(value, HashMap::from([(2u8, 20u32)]));
    }

    #[test]
    fn derive_accepts_raw_option_paths() {
        let _: fn(&mut OptionOuter, <OptionOuter as Recallable>::Memento) =
            <OptionOuter as Recall>::recall;
    }

    #[test]
    fn derive_accepts_parameterized_path_types() {
        let _: fn(&mut GenericOuter, <GenericOuter as Recallable>::Memento) =
            <GenericOuter as Recall>::recall;
    }

    #[test]
    fn derive_accepts_raw_hash_map_paths() {
        let _: fn(&mut HashMapOuter, <HashMapOuter as Recallable>::Memento) =
            <HashMapOuter as Recall>::recall;
    }
}