Skip to main content

ploidy_util/
absent.rs

1use std::{marker::PhantomData, ops::Deref};
2
3#[cfg(feature = "did-you-mean")]
4use ploidy_pointer::JsonPointeeType;
5use ploidy_pointer::{JsonPointee, JsonPointeeError, JsonPointer, JsonPointerTypeError};
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7
8/// An [`Option`]-like type that distinguishes between
9/// "value not present" and "value present but `null`".
10#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
11pub enum AbsentOr<T> {
12    #[default]
13    Absent,
14    Null,
15    Present(T),
16}
17
18impl<T> AbsentOr<T> {
19    /// Returns `true` if the value is [`Absent`](Self::Absent).
20    #[inline]
21    pub fn is_absent(&self) -> bool {
22        matches!(self, Self::Absent)
23    }
24
25    /// Returns `true` if the value is [`Null`](Self::Null).
26    #[inline]
27    pub fn is_null(&self) -> bool {
28        matches!(self, Self::Null)
29    }
30
31    /// Returns `true` if the value is [`Present`](Self::Present).
32    #[inline]
33    pub fn is_present(&self) -> bool {
34        matches!(self, Self::Present(_))
35    }
36
37    /// Converts this [`AbsentOr`] into a [`Result`], mapping
38    /// [`Present`] to [`Ok`], and both [`Absent`] and
39    /// [`Null`] to [`AbsentError`].
40    ///
41    /// [`Present`]: Self::Present
42    /// [`Absent`]: Self::Absent
43    /// [`Null`]: Self::Null
44    #[inline]
45    pub fn ok(self) -> Result<T, AbsentError> {
46        match self {
47            Self::Absent => Err(AbsentError::Absent),
48            Self::Null => Err(AbsentError::Null),
49            Self::Present(value) => Ok(value),
50        }
51    }
52
53    /// Converts from `&AbsentOr<T>` to `AbsentOr<&T>`.
54    #[inline]
55    pub fn as_ref(&self) -> AbsentOr<&T> {
56        match self {
57            Self::Absent => AbsentOr::Absent,
58            Self::Null => AbsentOr::Null,
59            Self::Present(value) => AbsentOr::Present(value),
60        }
61    }
62
63    /// Applies `f` to the contained value if [`Present`],
64    /// leaving [`Absent`] and [`Null`] untouched.
65    ///
66    /// [`Present`]: Self::Present
67    /// [`Absent`]: Self::Absent
68    /// [`Null`]: Self::Null
69    #[inline]
70    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> AbsentOr<U> {
71        match self {
72            Self::Absent => AbsentOr::Absent,
73            Self::Null => AbsentOr::Null,
74            Self::Present(value) => AbsentOr::Present(f(value)),
75        }
76    }
77
78    /// Applies `f` to the contained value if [`Present`](Self::Present),
79    /// or returns `default` otherwise.
80    #[inline]
81    pub fn map_or<U>(self, default: U, f: impl FnOnce(T) -> U) -> U {
82        match self {
83            Self::Absent | Self::Null => default,
84            Self::Present(value) => f(value),
85        }
86    }
87
88    /// Applies `f` to the contained value if [`Present`](Self::Present),
89    /// or computes a `default` otherwise.
90    #[inline]
91    pub fn map_or_else<U>(self, default: impl FnOnce() -> U, f: impl FnOnce(T) -> U) -> U {
92        match self {
93            Self::Absent | Self::Null => default(),
94            Self::Present(value) => f(value),
95        }
96    }
97
98    /// Returns `other` if `self` is [`Present`], or propagates
99    /// [`Absent`] and [`Null`].
100    ///
101    /// [`Present`]: Self::Present
102    /// [`Absent`]: Self::Absent
103    /// [`Null`]: Self::Null
104    #[inline]
105    pub fn and<U>(self, other: AbsentOr<U>) -> AbsentOr<U> {
106        match self {
107            Self::Absent => AbsentOr::Absent,
108            Self::Null => AbsentOr::Null,
109            Self::Present(_) => other,
110        }
111    }
112
113    /// Returns the result of applying `f` to the contained value
114    /// if [`Present`], or propagates [`Absent`] and [`Null`].
115    ///
116    /// [`Present`]: Self::Present
117    /// [`Absent`]: Self::Absent
118    /// [`Null`]: Self::Null
119    #[inline]
120    pub fn and_then<U>(self, f: impl FnOnce(T) -> AbsentOr<U>) -> AbsentOr<U> {
121        match self {
122            Self::Absent => AbsentOr::Absent,
123            Self::Null => AbsentOr::Null,
124            Self::Present(value) => f(value),
125        }
126    }
127
128    /// Returns `self` if [`Present`](Self::Present), or `other`
129    /// otherwise.
130    #[inline]
131    pub fn or(self, other: AbsentOr<T>) -> AbsentOr<T> {
132        match self {
133            Self::Present(_) => self,
134            Self::Absent | Self::Null => other,
135        }
136    }
137
138    /// Returns `self` if [`Present`](Self::Present), or computes
139    /// a fallback from `f` otherwise.
140    #[inline]
141    pub fn or_else(self, f: impl FnOnce() -> AbsentOr<T>) -> AbsentOr<T> {
142        match self {
143            Self::Present(_) => self,
144            Self::Absent | Self::Null => f(),
145        }
146    }
147
148    /// Returns the contained value if [`Present`](Self::Present),
149    /// or the provided `default` otherwise.
150    #[inline]
151    pub fn unwrap_or(self, default: T) -> T {
152        match self {
153            Self::Absent | Self::Null => default,
154            Self::Present(value) => value,
155        }
156    }
157
158    /// Returns the contained value if [`Present`](Self::Present),
159    /// or computes a default from `f` otherwise.
160    #[inline]
161    pub fn unwrap_or_else(self, f: impl FnOnce() -> T) -> T {
162        match self {
163            Self::Absent | Self::Null => f(),
164            Self::Present(value) => value,
165        }
166    }
167
168    /// Converts this [`AbsentOr`] into an [`Option`],
169    /// collapsing [`Absent`] and [`Null`] into [`None`].
170    ///
171    /// [`Absent`]: Self::Absent
172    /// [`Null`]: Self::Null
173    #[inline]
174    pub fn into_option(self) -> Option<T> {
175        match self {
176            Self::Absent | Self::Null => None,
177            Self::Present(value) => Some(value),
178        }
179    }
180}
181
182impl<T: Deref> AbsentOr<T> {
183    /// Converts from `AbsentOr<T>` to `AbsentOr<&T::Target>`.
184    #[inline]
185    pub fn as_deref(&self) -> AbsentOr<&T::Target> {
186        match self {
187            Self::Absent => AbsentOr::Absent,
188            Self::Null => AbsentOr::Null,
189            Self::Present(value) => AbsentOr::Present(value),
190        }
191    }
192}
193
194impl<T: Default> AbsentOr<T> {
195    /// Returns the contained value if [`Present`](Self::Present),
196    /// or the default value of `T` otherwise.
197    #[inline]
198    pub fn unwrap_or_default(self) -> T {
199        match self {
200            Self::Absent | Self::Null => T::default(),
201            Self::Present(value) => value,
202        }
203    }
204}
205
206impl<T> From<T> for AbsentOr<T> {
207    #[inline]
208    fn from(value: T) -> Self {
209        Self::Present(value)
210    }
211}
212
213/// Transparently resolves a [`JsonPointer`] against the contained value
214/// if [`Present`], or returns an error if [`Absent`] or [`Null`].
215///
216/// [`Present`]: Self::Present
217/// [`Absent`]: Self::Absent
218/// [`Null`]: Self::Null
219impl<T: JsonPointee> JsonPointee for AbsentOr<T> {
220    fn resolve(&self, pointer: &JsonPointer) -> Result<&dyn JsonPointee, JsonPointeeError> {
221        match self {
222            Self::Present(value) => value.resolve(pointer),
223            _ => Err({
224                #[cfg(feature = "did-you-mean")]
225                let err = JsonPointerTypeError::with_ty(pointer, JsonPointeeType::name_of(self));
226                #[cfg(not(feature = "did-you-mean"))]
227                let err = JsonPointerTypeError::new(pointer);
228                err
229            })?,
230        }
231    }
232}
233
234impl<T: Serialize> Serialize for AbsentOr<T> {
235    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
236        match self {
237            Self::Absent | Self::Null => serializer.serialize_none(),
238            Self::Present(value) => serializer.serialize_some(value),
239        }
240    }
241}
242
243impl<'de, T: Deserialize<'de>> Deserialize<'de> for AbsentOr<T> {
244    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
245        struct Visitor<T>(PhantomData<T>);
246        impl<'de, T: Deserialize<'de>> serde::de::Visitor<'de> for Visitor<T> {
247            type Value = AbsentOr<T>;
248
249            fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250                f.write_str("`null` or value")
251            }
252
253            fn visit_unit<E: serde::de::Error>(self) -> Result<Self::Value, E> {
254                Ok(AbsentOr::Null)
255            }
256
257            fn visit_none<E: serde::de::Error>(self) -> Result<Self::Value, E> {
258                Ok(AbsentOr::Null)
259            }
260
261            fn visit_some<D: Deserializer<'de>>(
262                self,
263                deserializer: D,
264            ) -> Result<Self::Value, D::Error> {
265                T::deserialize(deserializer).map(AbsentOr::Present)
266            }
267        }
268        deserializer.deserialize_option(Visitor(PhantomData))
269    }
270}
271
272#[derive(Debug, thiserror::Error)]
273pub enum AbsentError {
274    #[error("value not present")]
275    Absent,
276    #[error("value is `null`")]
277    Null,
278}
279
280impl AbsentError {
281    /// Attaches a field name to this [`AbsentError`], producing a
282    /// [`FieldAbsentError`] suitable for user-facing diagnostics
283    /// when a specific field isn't [`Present`](AbsentOr::Present).
284    #[inline]
285    pub fn field(self, name: &'static str) -> FieldAbsentError {
286        match self {
287            Self::Absent => FieldAbsentError::Absent(name),
288            Self::Null => FieldAbsentError::Null(name),
289        }
290    }
291}
292
293#[derive(Debug, thiserror::Error)]
294pub enum FieldAbsentError {
295    #[error("field `{0}` not present")]
296    Absent(&'static str),
297    #[error("field `{0}` is `null`")]
298    Null(&'static str),
299}
300
301#[cfg(test)]
302mod tests {
303    use ploidy_pointer::{JsonPointee, JsonPointeeExt, JsonPointerTarget};
304
305    use super::*;
306
307    #[derive(JsonPointee, JsonPointerTarget)]
308    #[ploidy(pointer(untagged))]
309    enum Response {
310        One(ResponseOne),
311        Two(ResponseTwo),
312    }
313
314    #[derive(JsonPointee, JsonPointerTarget)]
315    struct ResponseOne {
316        data: AbsentOr<String>,
317        error: AbsentOr<ResponseError>,
318    }
319
320    #[derive(JsonPointee, JsonPointerTarget)]
321    struct ResponseTwo {
322        data: AbsentOr<i32>,
323        error: AbsentOr<ResponseError>,
324    }
325
326    #[derive(JsonPointee, JsonPointerTarget)]
327    struct ResponseError {
328        message: String,
329    }
330
331    #[test]
332    fn test_absent_or_present_pointer_succeeds() {
333        let response = Response::One(ResponseOne {
334            error: AbsentOr::Present(ResponseError {
335                message: "oops".to_owned(),
336            }),
337            data: AbsentOr::Null,
338        });
339
340        // `error` is present, so the pointer should resolve.
341        let err = response.pointer::<&ResponseError>("/error").unwrap();
342        assert_eq!(err.message, "oops");
343    }
344
345    #[test]
346    fn test_absent_or_null_errors() {
347        let response = Response::Two(ResponseTwo {
348            data: AbsentOr::Present(2),
349            error: AbsentOr::Null,
350        });
351
352        // The `AbsentOr` wrapper is transparent, and `Null` has no value,
353        // so resolving the pointer always errors.
354        let result = response.pointer::<&ResponseError>("/error");
355        assert!(result.is_err());
356    }
357
358    #[test]
359    fn test_absent_or_absent_errors() {
360        let response = Response::Two(ResponseTwo {
361            data: AbsentOr::Present(2),
362            error: AbsentOr::Absent,
363        });
364
365        // `AbsentOr::Absent` behaves the same as `Null`.
366        let result = response.pointer::<&ResponseError>("/error");
367        assert!(result.is_err());
368    }
369}