struct_patch/traits.rs
1/// A struct that a patch can be applied to
2///
3/// Deriving [`Patch`] will generate a patch struct and an accompanying trait impl so that it can be applied to the original struct.
4/// ```rust
5/// # use struct_patch::Patch;
6/// #[derive(Patch)]
7/// struct Item {
8/// field_bool: bool,
9/// field_int: usize,
10/// field_string: String,
11/// }
12///
13/// // Generated struct
14/// // struct ItemPatch {
15/// // field_bool: Option<bool>,
16/// // field_int: Option<usize>,
17/// // field_string: Option<String>,
18/// // }
19/// ```
20/// ## Container attributes
21/// ### `#[patch(attribute(derive(...)))]`
22/// Use this attribute to derive traits on the generated patch struct
23/// ```rust
24/// # use struct_patch::Patch;
25/// # use serde::{Serialize, Deserialize};
26/// #[derive(Patch)]
27/// #[patch(attribute(derive(Debug, Default, Deserialize, Serialize)))]
28/// struct Item;
29///
30/// // Generated struct
31/// // #[derive(Debug, Default, Deserialize, Serialize)]
32/// // struct ItemPatch {}
33/// ```
34///
35/// ### `#[patch(attribute(...))]`
36/// Use this attribute to pass the attributes on the generated patch struct
37/// ```compile_fail
38/// // This example need `serde` and `serde_with` crates
39/// # use struct_patch::Patch;
40/// #[derive(Patch, Debug)]
41/// #[patch(attribute(derive(Serialize, Deserialize, Default)))]
42/// #[patch(attribute(skip_serializing_none))]
43/// struct Item;
44///
45/// // Generated struct
46/// // #[derive(Default, Deserialize, Serialize)]
47/// // #[skip_serializing_none]
48/// // struct ItemPatch {}
49/// ```
50///
51/// ### `#[patch(name = "...")]`
52/// Use this attribute to change the name of the generated patch struct
53/// ```rust
54/// # use struct_patch::Patch;
55/// #[derive(Patch)]
56/// #[patch(name = "ItemOverlay")]
57/// struct Item { }
58///
59/// // Generated struct
60/// // struct ItemOverlay {}
61/// ```
62///
63/// ### `#[patch(default_log(fn_path))]`
64/// Automatically call `fn_path` with patched field information inside
65/// every generated `apply` call. Has no effect on `apply_with_log`. The path
66/// may be any function path visible at the call site.
67///
68/// When the `nesting` feature is enabled, `fn_path` receives both a prefix path and field name.
69/// When `nesting` is disabled, `fn_path` receives only the field name.
70///
71/// ```rust
72/// # use struct_patch::Patch;
73/// #[derive(Default, Patch)]
74/// struct Item {
75/// field_int: usize,
76/// field_string: String,
77/// }
78///
79/// let mut item = Item::default();
80/// let patch = ItemPatch { field_int: Some(1), field_string: None };
81///
82/// #[cfg(feature = "nesting")]
83/// {
84/// fn log_field(prefixes: &[&str], field: &str) {
85/// let path = if prefixes.is_empty() {
86/// field.to_string()
87/// } else {
88/// format!("{}.{}", prefixes.join("."), field)
89/// };
90/// println!("patched: {path}");
91/// }
92///
93/// #[derive(Default, Patch)]
94/// #[patch(default_log(log_field))]
95/// struct Config {
96/// field_int: usize,
97/// field_string: String,
98/// }
99/// let mut config = Config::default();
100/// config.apply(ConfigPatch { field_int: Some(1), field_string: None });
101/// // log_field(&[], "field_int") is called automatically
102/// }
103///
104/// #[cfg(not(feature = "nesting"))]
105/// {
106/// fn log_field(field: &str) {
107/// println!("patched: {field}");
108/// }
109///
110/// #[derive(Default, Patch)]
111/// #[patch(default_log(log_field))]
112/// struct Config {
113/// field_int: usize,
114/// field_string: String,
115/// }
116/// let mut config = Config::default();
117/// config.apply(ConfigPatch { field_int: Some(1), field_string: None });
118/// // log_field("field_int") is called automatically
119/// }
120/// ```
121///
122/// ## Field attributes
123/// ### `#[patch(skip)]`
124/// If you want certain fields to be unpatchable, you can let the derive macro skip certain fields when creating the patch struct
125/// ```rust
126/// # use struct_patch::Patch;
127/// #[derive(Patch)]
128/// struct Item {
129/// #[patch(skip)]
130/// id: String,
131/// data: String,
132/// }
133///
134/// // Generated struct
135/// // struct ItemPatch {
136/// // data: Option<String>,
137/// // }
138/// ```
139///
140/// ### `#[patch(skip_wrap)]`
141/// Keep the field type as-is in the generated patch struct (no extra `Option`
142/// wrapping). This is useful for fields that are already `Option<...>`,
143/// typically `Option<Vec<_>>`, where the default double-`Option` in the patch
144/// is unwanted. With `skip_wrap`, `None` in the patch means "no change" and
145/// `Some(v)` sets the field to `Some(v)` (including `Some(vec![])` to clear
146/// the vector). Cannot be combined with `empty_value`.
147/// ```rust
148/// # use struct_patch::Patch;
149/// #[derive(Default, Patch)]
150/// struct Item {
151/// #[patch(skip_wrap)]
152/// tags: Option<Vec<String>>,
153/// }
154///
155/// // Generated struct
156/// // struct ItemPatch {
157/// // tags: Option<Vec<String>>, // not wrapped again
158/// // }
159///
160/// let mut item = Item { tags: Some(vec!["a".into()]) };
161///
162/// // `None` in the patch keeps the field unchanged.
163/// item.apply(ItemPatch { tags: None });
164/// assert_eq!(item.tags, Some(vec!["a".into()]));
165///
166/// // `Some(vec![])` still applies and clears the list.
167/// item.apply(ItemPatch { tags: Some(vec![]) });
168/// assert_eq!(item.tags, Some(vec![]));
169/// ```
170pub trait Patch<P> {
171 /// Apply a patch
172 fn apply(&mut self, patch: P);
173
174 /// Apply a patch, calling `log` with each patched field name.
175 ///
176 /// The default implementation ignores `log` and delegates to [`apply`](Patch::apply).
177 /// The derive macro generates an override that calls `log` once per field that is
178 /// actually changed.
179 ///
180 /// When the `nesting` feature is enabled, `log` receives both a prefix path and field name.
181 /// When `nesting` is disabled, `log` receives only the field name.
182 ///
183 /// ```rust
184 /// # use struct_patch::Patch;
185 /// #[derive(Default, Patch)]
186 /// struct Item {
187 /// field_int: usize,
188 /// field_string: String,
189 /// }
190 ///
191 /// let mut item = Item::default();
192 /// let patch = ItemPatch { field_int: Some(42), field_string: None };
193 ///
194 /// let mut patched_fields = Vec::new();
195 /// #[cfg(feature = "nesting")]
196 /// item.apply_with_log(patch, |prefixes, field| {
197 /// let path = if prefixes.is_empty() {
198 /// field.to_string()
199 /// } else {
200 /// format!("{}.{}", prefixes.join("."), field)
201 /// };
202 /// patched_fields.push(path);
203 /// });
204 ///
205 /// #[cfg(not(feature = "nesting"))]
206 /// item.apply_with_log(patch, |field| {
207 /// patched_fields.push(field.to_string());
208 /// });
209 ///
210 /// assert_eq!(patched_fields, vec!["field_int"]);
211 /// ```
212 #[cfg(feature = "nesting")]
213 fn apply_with_log<F: FnMut(&[&str], &str)>(&mut self, patch: P, _log: F) {
214 self.apply(patch);
215 }
216
217 #[cfg(not(feature = "nesting"))]
218 fn apply_with_log<F: FnMut(&str)>(&mut self, patch: P, _log: F) {
219 self.apply(patch);
220 }
221
222 /// Returns a patch that when applied turns any struct of the same type into `Self`
223 fn into_patch(self) -> P;
224
225 /// Returns a patch that when applied turns `previous_struct` into `Self`
226 fn into_patch_by_diff(self, previous_struct: Self) -> P;
227
228 /// Get an empty patch instance
229 fn new_empty_patch() -> P;
230}
231
232pub trait Filler<F> {
233 /// Apply a filler
234 fn apply(&mut self, filler: F);
235
236 /// Apply a filler, calling `log` with each field name that is actually filled.
237 ///
238 /// The default implementation ignores `log` and delegates to [`apply`](Filler::apply).
239 /// The derive macro generates an override that calls `log` once per field that is
240 /// actually filled (i.e. the field was empty and the filler supplied a value).
241 ///
242 /// When the `nesting` feature is enabled, `log` receives both a prefix path and field name.
243 /// When `nesting` is disabled, `log` receives only the field name.
244 ///
245 /// ```rust
246 /// # use struct_patch::Filler;
247 /// #[derive(Default, Filler)]
248 /// struct Item {
249 /// value: Option<usize>,
250 /// }
251 ///
252 /// let mut item = Item::default();
253 /// let filler = ItemFiller { value: Some(42) };
254 ///
255 /// let mut filled_fields = Vec::new();
256 /// #[cfg(feature = "nesting")]
257 /// item.apply_with_log(filler, |prefixes, field| {
258 /// let path = if prefixes.is_empty() {
259 /// field.to_string()
260 /// } else {
261 /// format!("{}.{}", prefixes.join("."), field)
262 /// };
263 /// filled_fields.push(path);
264 /// });
265 ///
266 /// #[cfg(not(feature = "nesting"))]
267 /// item.apply_with_log(filler, |field| {
268 /// filled_fields.push(field.to_string());
269 /// });
270 ///
271 /// assert_eq!(filled_fields, vec!["value"]);
272 /// ```
273 #[cfg(feature = "nesting")]
274 fn apply_with_log<L: FnMut(&[&str], &str)>(&mut self, filler: F, _log: L) {
275 self.apply(filler);
276 }
277
278 #[cfg(not(feature = "nesting"))]
279 fn apply_with_log<L: FnMut(&str)>(&mut self, filler: F, _log: L) {
280 self.apply(filler);
281 }
282
283 /// Get an empty filler instance
284 fn new_empty_filler() -> F;
285}
286
287#[cfg(feature = "status")]
288/// A patch struct with extra status information
289pub trait Status {
290 /// Returns `true` if all fields are `None`, `false` otherwise.
291 fn is_empty(&self) -> bool;
292}
293
294#[cfg(feature = "merge")]
295/// A patch struct that can be merged to another one
296pub trait Merge {
297 fn merge(self, other: Self) -> Self;
298}
299
300#[cfg(feature = "substrate")]
301/// A substrate struct that can expose the fields information thereof
302pub trait Substrate {
303 fn expose_content() -> &'static str;
304
305 /// Expose the field information, by call this function in Build.rs
306 fn expose();
307}
308
309#[cfg(feature = "catalyst")]
310/// A catalyst struct that can expose the fields information thereof
311pub trait Catalyst<S, Cpx> {
312 /// catalyst bind on substrate and generate complex
313 fn bind(self, substrate: S) -> Cpx;
314}
315
316#[cfg(feature = "catalyst")]
317/// A complex struct that can decouple return catalyst and substrate
318pub trait Complex<Cat, S> {
319 /// complex decouple to catalyst and substrate
320 fn decouple(self) -> (Cat, S);
321}