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}