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