Skip to main content

patchable/
lib.rs

1//! # Patchable
2//!
3//! A crate for handling partial updates to data structures.
4//!
5//! This crate provides the [`Patchable`], [`Patch`], and [`TryPatch`] traits, along with
6//! derive macros for `Patchable` and `Patch`, and an attribute macro `patchable_model`
7//! re-exported from `patchable_macro` for easy derivation.
8//!
9//! ## Motivation
10//!
11//! Many systems receive incremental updates where only a subset of fields change or can be
12//! considered part of the state. This crate formalizes this pattern by defining a patch type for a
13//! structure and providing a consistent way to apply such patches safely.
14
15// Re-export the derive macros.
16pub use patchable_macro::{Patch, Patchable, patchable_model};
17
18/// A type that declares a companion patch type.
19///
20/// ## Usage
21///
22/// ```rust
23/// use patchable::{Patch, Patchable};
24/// use serde::{Deserialize, Serialize};
25///
26/// #[derive(Debug, Serialize)]
27/// pub struct Accumulator<T> {
28///     prev_control_signal: T,
29///     #[serde(skip)]
30///     filter: fn(&i32) -> bool,
31///     accumulated: u32,
32/// }
33///
34/// //vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
35/// // If we derive `Patchable` and `Patch` for `Accumulator`, the following `AccumulatorPatch` type
36/// // and the `Patchable`/`Patch` implementations can be generated automatically.
37/// //
38/// // When deriving `Patchable`, a `From<Accumulator>` implementation is generated if the
39/// // `impl_from` feature is enabled. For derived implementations, mark non-state fields with
40/// // `#[patchable(skip)]` (and add `#[serde(skip)]` as needed when using serde).
41///
42/// // Derive `Clone` if needed by enabling "cloneable" feature or manually.
43/// // "cloneable" is enabled by default.
44/// #[derive(PartialEq, Deserialize)]
45/// pub struct AccumulatorPatch<T> {
46///     prev_control_signal: T,
47///     accumulated: u32,
48/// }
49///
50/// impl<T> Patchable for Accumulator<T> {
51///     type Patch = AccumulatorPatch<T>;
52/// }
53///
54/// impl<T> From<Accumulator<T>> for AccumulatorPatch<T> {
55///     fn from(acc: Accumulator<T>) -> Self {
56///         Self {
57///             prev_control_signal: acc.prev_control_signal,
58///             accumulated: acc.accumulated,
59///         }
60///     }
61/// }
62///
63/// impl<T> Patch for Accumulator<T> {
64///     #[inline(always)]
65///     fn patch(&mut self, patch: Self::Patch) {
66///         self.prev_control_signal = patch.prev_control_signal;
67///         self.accumulated = patch.accumulated;
68///     }
69/// }
70/// //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
71///
72/// let mut accumulator = Accumulator {
73///     prev_control_signal: -1,
74///     filter: |x: &i32| *x > 300,
75///     accumulated: 0,
76/// };
77///
78/// let accumulator_patch: AccumulatorPatch<i32> = serde_json::from_str(
79///     r#"{
80///         "prev_control_signal": 6,
81///         "accumulated": 15
82///     }"#
83/// ).unwrap();
84///
85/// accumulator.patch(accumulator_patch);
86///
87/// assert_eq!(accumulator.prev_control_signal, 6i32);
88/// assert_eq!(accumulator.accumulated, 15u32);
89/// ```
90/// Declares the associated patch type.
91pub trait Patchable {
92    /// The type of patch associated with this structure.
93    type Patch;
94}
95
96/// A type that can be updated using its companion patch.
97pub trait Patch: Patchable {
98    /// Applies the given patch to update the structure.
99    fn patch(&mut self, patch: Self::Patch);
100}
101
102/// A fallible variant of [`Patch`].
103///
104/// This trait lets you apply a patch with validation and return a custom error
105/// if it cannot be applied.
106///
107/// ## Usage
108///
109/// ```rust
110/// use patchable::{TryPatch, Patchable};
111/// use std::fmt;
112///
113/// #[derive(Debug)]
114/// struct Config {
115///     concurrency: u32,
116/// }
117///
118/// #[derive(Clone, PartialEq)]
119/// struct ConfigPatch {
120///     concurrency: u32,
121/// }
122///
123/// #[derive(Debug)]
124/// struct PatchError(String);
125///
126/// impl fmt::Display for PatchError {
127///     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128///         write!(f, "{}", self.0)
129///     }
130/// }
131///
132/// impl std::error::Error for PatchError {}
133///
134/// impl Patchable for Config {
135///     type Patch = ConfigPatch;
136/// }
137///
138/// impl From<Config> for ConfigPatch {
139///     fn from(c: Config) -> Self {
140///         Self { concurrency: c.concurrency }
141///     }
142/// }
143///
144/// impl TryPatch for Config {
145///     type Error = PatchError;
146///
147///     fn try_patch(&mut self, patch: Self::Patch) -> Result<(), Self::Error> {
148///         if patch.concurrency == 0 {
149///             return Err(PatchError("Concurrency must be > 0".into()));
150///         }
151///         self.concurrency = patch.concurrency;
152///         Ok(())
153///     }
154/// }
155///
156/// let mut config = Config { concurrency: 1 };
157/// let valid_patch = ConfigPatch { concurrency: 4 };
158/// config.try_patch(valid_patch).unwrap();
159/// assert_eq!(config.concurrency, 4);
160///
161/// let invalid_patch = ConfigPatch { concurrency: 0 };
162/// assert!(config.try_patch(invalid_patch).is_err());
163/// ```
164pub trait TryPatch: Patchable {
165    /// The error type returned when applying a patch fails.
166    type Error: std::error::Error + Send + Sync + 'static;
167
168    /// Applies the provided patch to `self`.
169    ///
170    /// # Errors
171    ///
172    /// Returns an error if the patch is invalid or cannot be applied.
173    fn try_patch(&mut self, patch: Self::Patch) -> Result<(), Self::Error>;
174}
175
176/// Blanket implementation for all [`Patch`] types, where patching is
177/// infallible.
178impl<T: Patch> TryPatch for T {
179    type Error = std::convert::Infallible;
180
181    #[inline(always)]
182    fn try_patch(&mut self, patch: Self::Patch) -> Result<(), Self::Error> {
183        self.patch(patch);
184        Ok(())
185    }
186}