model_views/
patch.rs

1//! Provides utilities for handling optional updates to values in a type-safe way.
2//! The `Patch` enum represents either an update with a new value or an explicit ignore
3//! instruction, making it clearer than using `Option` for update operations.
4
5/// Represents a potential update to a value, either providing a new value or explicitly
6/// indicating that the value should be ignored/unchanged.
7///
8/// This is useful in PATCH-style updates where some fields should be updated while others
9/// remain unchanged. Unlike `Option`, `Patch` makes the intent to ignore a value explicit.
10#[derive(Default, Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
11pub enum Patch<T> {
12    /// Explicitly indicates that the existing value should remain unchanged
13    #[default]
14    Ignore,
15    /// Contains a value that should replace the existing value
16    Update(T),
17}
18
19impl<T> Patch<T> {
20    /// Creates a new `Patch` variant that will update to the given value
21    pub const fn update(value: T) -> Self {
22        Self::Update(value)
23    }
24
25    /// Creates a new `Patch::Ignore` variant indicating no update should occur
26    pub const fn ignore() -> Self {
27        Self::Ignore
28    }
29
30    pub const fn is_ignore(&self) -> bool {
31        matches!(self, Self::Ignore)
32    }
33
34    /// Returns a new `Patch` that references the inner value without taking ownership
35    pub const fn as_ref(&self) -> Patch<&T> {
36        match self {
37            Self::Update(value) => Patch::Update(value),
38            Self::Ignore => Patch::Ignore,
39        }
40    }
41
42    /// Converts the `Patch` into an `Option` that borrows the inner value
43    pub const fn as_option_ref(&self) -> Option<&T> {
44        match self {
45            Self::Update(value) => Some(value),
46            Self::Ignore => None,
47        }
48    }
49
50    /// Converts the `Patch` into an `Option` containing a clone of the inner value
51    pub fn as_option(&self) -> Option<T>
52    where
53        T: Clone,
54    {
55        match self {
56            Self::Update(value) => Some(value.clone()),
57            Self::Ignore => None,
58        }
59    }
60
61    /// Converts the `Patch` into an `Option`, consuming self
62    pub fn into_option(self) -> Option<T> {
63        match self {
64            Self::Update(value) => Some(value),
65            Self::Ignore => None,
66        }
67    }
68}
69
70impl<T> From<Patch<T>> for Option<T> {
71    fn from(value: Patch<T>) -> Self {
72        value.into_option()
73    }
74}
75
76impl<T> From<Option<T>> for Patch<T> {
77    fn from(value: Option<T>) -> Self {
78        value.map_or_else(|| Self::Ignore, |value| Self::Update(value))
79    }
80}
81
82impl<T> PartialEq<Option<T>> for Patch<T>
83where
84    T: PartialEq,
85{
86    fn eq(&self, other: &Option<T>) -> bool {
87        match (self, other) {
88            (Self::Update(value), Some(other)) => value == other,
89            (Self::Ignore, None) => true,
90            _ => false,
91        }
92    }
93}
94
95#[cfg(feature = "serde")]
96mod serde {
97    use super::Patch;
98    use serde::{Deserialize, Serialize};
99
100    impl<T> Serialize for Patch<T>
101    where
102        T: Serialize,
103    {
104        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
105        where
106            S: serde::Serializer,
107        {
108            match self {
109                Self::Update(v) => serializer.serialize_some(v),
110                Self::Ignore => serializer.serialize_none(),
111            }
112        }
113    }
114
115    impl<'de, T> Deserialize<'de> for Patch<T>
116    where
117        T: Deserialize<'de>,
118    {
119        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
120        where
121            D: serde::Deserializer<'de>,
122        {
123            // Delegate to Option<T> and map back into Patch<T>
124            let opt = Option::<T>::deserialize(deserializer)?;
125            Ok(opt.map_or_else(|| Self::Ignore, |v| Self::Update(v)))
126        }
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn test_basic_functionality() {
136        let update = Patch::update(42);
137        let ignore: Patch<i32> = Patch::ignore();
138
139        assert!(matches!(update, Patch::Update(42)));
140        assert!(matches!(ignore, Patch::Ignore));
141    }
142
143    #[test]
144    fn test_option_conversions() {
145        let update: Patch<i32> = Patch::update(42);
146        let ignore: Patch<i32> = Patch::ignore();
147
148        assert_eq!(Option::from(update), Some(42));
149        assert_eq!(Option::from(ignore), None::<i32>);
150
151        assert_eq!(Patch::from(Some(42)), Patch::Update(42));
152        assert_eq!(Patch::from(None::<i32>), Patch::Ignore);
153    }
154
155    #[test]
156    fn test_reference_operations() {
157        let update = Patch::update(42);
158        let ignore: Patch<i32> = Patch::ignore();
159
160        assert_eq!(update.as_ref(), Patch::Update(&42));
161        assert_eq!(ignore.as_ref(), Patch::Ignore);
162
163        assert_eq!(update.as_option_ref(), Some(&42));
164        assert_eq!(ignore.as_option_ref(), None);
165    }
166
167    #[test]
168    #[allow(clippy::default_trait_access)]
169    fn test_default() {
170        let patch: Patch<i32> = Default::default();
171        assert!(matches!(patch, Patch::Ignore));
172    }
173
174    #[test]
175    fn test_equality() {
176        let update = Patch::update(42);
177        let ignore: Patch<i32> = Patch::ignore();
178
179        assert_eq!(update, Some(42));
180        assert_ne!(update, None);
181        assert_eq!(ignore, None);
182        assert_ne!(ignore, Some(42));
183    }
184}