agent_client_protocol_schema/
maybe_undefined.rs

1//! Based on: <https://docs.rs/async-graphql/latest/src/async_graphql/types/maybe_undefined.rs.html>
2use std::{
3    borrow::Cow,
4    ffi::OsStr,
5    ops::Deref,
6    path::{Path, PathBuf},
7    sync::Arc,
8};
9
10use schemars::JsonSchema;
11use serde::{Deserialize, Deserializer, Serialize, Serializer};
12
13/// Similar to `Option`, but it has three states, `undefined`, `null` and `x`.
14///
15/// When using with Serde, you will likely want to skip serialization of `undefined`
16/// and add a `default` for deserialization.
17///
18/// # Example
19///
20/// ```rust
21/// use agent_client_protocol_schema::MaybeUndefined;
22/// use serde::{Serialize, Deserialize};
23///
24/// #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
25/// struct A {
26///     #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
27///     a: MaybeUndefined<i32>,
28/// }
29/// ```
30#[allow(missing_docs)]
31#[derive(Copy, Clone, Default, PartialEq, PartialOrd, Eq, Ord, Debug, Hash, JsonSchema)]
32#[schemars(with = "Option<Option<T>>", inline)]
33#[expect(clippy::exhaustive_enums)]
34pub enum MaybeUndefined<T> {
35    #[default]
36    Undefined,
37    Null,
38    Value(T),
39}
40
41impl<T> MaybeUndefined<T> {
42    /// Returns true if the `MaybeUndefined<T>` is undefined.
43    #[inline]
44    pub const fn is_undefined(&self) -> bool {
45        matches!(self, MaybeUndefined::Undefined)
46    }
47
48    /// Returns true if the `MaybeUndefined<T>` is null.
49    #[inline]
50    pub const fn is_null(&self) -> bool {
51        matches!(self, MaybeUndefined::Null)
52    }
53
54    /// Returns true if the `MaybeUndefined<T>` contains value.
55    #[inline]
56    pub const fn is_value(&self) -> bool {
57        matches!(self, MaybeUndefined::Value(_))
58    }
59
60    /// Borrow the value, returns `None` if the the `MaybeUndefined<T>` is
61    /// `undefined` or `null`, otherwise returns `Some(T)`.
62    #[inline]
63    pub const fn value(&self) -> Option<&T> {
64        match self {
65            MaybeUndefined::Value(value) => Some(value),
66            _ => None,
67        }
68    }
69
70    /// Converts the `MaybeUndefined<T>` to `Option<T>`.
71    #[inline]
72    pub fn take(self) -> Option<T> {
73        match self {
74            MaybeUndefined::Value(value) => Some(value),
75            _ => None,
76        }
77    }
78
79    /// Converts the `MaybeUndefined<T>` to `Option<Option<T>>`.
80    #[inline]
81    pub const fn as_opt_ref(&self) -> Option<Option<&T>> {
82        match self {
83            MaybeUndefined::Undefined => None,
84            MaybeUndefined::Null => Some(None),
85            MaybeUndefined::Value(value) => Some(Some(value)),
86        }
87    }
88
89    /// Converts the `MaybeUndefined<T>` to `Option<Option<&U>>`.
90    #[inline]
91    pub fn as_opt_deref<U>(&self) -> Option<Option<&U>>
92    where
93        U: ?Sized,
94        T: Deref<Target = U>,
95    {
96        match self {
97            MaybeUndefined::Undefined => None,
98            MaybeUndefined::Null => Some(None),
99            MaybeUndefined::Value(value) => Some(Some(&**value)),
100        }
101    }
102
103    /// Returns `true` if the `MaybeUndefined<T>` contains the given value.
104    #[inline]
105    pub fn contains_value<U>(&self, x: &U) -> bool
106    where
107        U: PartialEq<T>,
108    {
109        match self {
110            MaybeUndefined::Value(y) => x == y,
111            _ => false,
112        }
113    }
114
115    /// Returns `true` if the `MaybeUndefined<T>` contains the given nullable
116    /// value.
117    #[inline]
118    pub fn contains<U>(&self, x: Option<&U>) -> bool
119    where
120        U: PartialEq<T>,
121    {
122        match self {
123            MaybeUndefined::Value(y) => matches!(x, Some(v) if v == y),
124            MaybeUndefined::Null => x.is_none(),
125            MaybeUndefined::Undefined => false,
126        }
127    }
128
129    /// Maps a `MaybeUndefined<T>` to `MaybeUndefined<U>` by applying a function
130    /// to the contained nullable value
131    #[inline]
132    pub fn map<U, F: FnOnce(Option<T>) -> Option<U>>(self, f: F) -> MaybeUndefined<U> {
133        match self {
134            MaybeUndefined::Value(v) => match f(Some(v)) {
135                Some(v) => MaybeUndefined::Value(v),
136                None => MaybeUndefined::Null,
137            },
138            MaybeUndefined::Null => match f(None) {
139                Some(v) => MaybeUndefined::Value(v),
140                None => MaybeUndefined::Null,
141            },
142            MaybeUndefined::Undefined => MaybeUndefined::Undefined,
143        }
144    }
145
146    /// Maps a `MaybeUndefined<T>` to `MaybeUndefined<U>` by applying a function
147    /// to the contained value
148    #[inline]
149    pub fn map_value<U, F: FnOnce(T) -> U>(self, f: F) -> MaybeUndefined<U> {
150        match self {
151            MaybeUndefined::Value(v) => MaybeUndefined::Value(f(v)),
152            MaybeUndefined::Null => MaybeUndefined::Null,
153            MaybeUndefined::Undefined => MaybeUndefined::Undefined,
154        }
155    }
156
157    /// Update `value` if the `MaybeUndefined<T>` is not undefined.
158    ///
159    /// # Example
160    ///
161    /// ```rust
162    /// use agent_client_protocol_schema::MaybeUndefined;
163    ///
164    /// let mut value = None;
165    ///
166    /// MaybeUndefined::Value(10i32).update_to(&mut value);
167    /// assert_eq!(value, Some(10));
168    ///
169    /// MaybeUndefined::Undefined.update_to(&mut value);
170    /// assert_eq!(value, Some(10));
171    ///
172    /// MaybeUndefined::Null.update_to(&mut value);
173    /// assert_eq!(value, None);
174    /// ```
175    pub fn update_to(self, value: &mut Option<T>) {
176        match self {
177            MaybeUndefined::Value(new) => *value = Some(new),
178            MaybeUndefined::Null => *value = None,
179            MaybeUndefined::Undefined => {}
180        }
181    }
182}
183
184impl<T, E> MaybeUndefined<Result<T, E>> {
185    /// Transposes a `MaybeUndefined` of a [`Result`] into a [`Result`] of a
186    /// `MaybeUndefined`.
187    ///
188    /// [`MaybeUndefined::Undefined`] will be mapped to
189    /// [`Ok`]`(`[`MaybeUndefined::Undefined`]`)`. [`MaybeUndefined::Null`]
190    /// will be mapped to [`Ok`]`(`[`MaybeUndefined::Null`]`)`.
191    /// [`MaybeUndefined::Value`]`(`[`Ok`]`(_))` and
192    /// [`MaybeUndefined::Value`]`(`[`Err`]`(_))` will be mapped to
193    /// [`Ok`]`(`[`MaybeUndefined::Value`]`(_))` and [`Err`]`(_)`.
194    ///
195    /// # Errors
196    ///
197    /// Returns an error if the input is [`MaybeUndefined::Value`]`(`[`Err`]`(_))`.
198    #[inline]
199    pub fn transpose(self) -> Result<MaybeUndefined<T>, E> {
200        match self {
201            MaybeUndefined::Undefined => Ok(MaybeUndefined::Undefined),
202            MaybeUndefined::Null => Ok(MaybeUndefined::Null),
203            MaybeUndefined::Value(Ok(v)) => Ok(MaybeUndefined::Value(v)),
204            MaybeUndefined::Value(Err(e)) => Err(e),
205        }
206    }
207}
208
209impl<T: Serialize> Serialize for MaybeUndefined<T> {
210    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
211        match self {
212            MaybeUndefined::Value(value) => value.serialize(serializer),
213            MaybeUndefined::Null => serializer.serialize_none(),
214            MaybeUndefined::Undefined => serializer.serialize_unit(),
215        }
216    }
217}
218
219impl<'de, T> Deserialize<'de> for MaybeUndefined<T>
220where
221    T: Deserialize<'de>,
222{
223    fn deserialize<D>(deserializer: D) -> Result<MaybeUndefined<T>, D::Error>
224    where
225        D: Deserializer<'de>,
226    {
227        Option::<T>::deserialize(deserializer).map(|value| match value {
228            Some(value) => MaybeUndefined::Value(value),
229            None => MaybeUndefined::Null,
230        })
231    }
232}
233
234impl<T> From<MaybeUndefined<T>> for Option<Option<T>> {
235    fn from(maybe_undefined: MaybeUndefined<T>) -> Self {
236        match maybe_undefined {
237            MaybeUndefined::Undefined => None,
238            MaybeUndefined::Null => Some(None),
239            MaybeUndefined::Value(value) => Some(Some(value)),
240        }
241    }
242}
243
244impl<T> From<Option<Option<T>>> for MaybeUndefined<T> {
245    fn from(value: Option<Option<T>>) -> Self {
246        match value {
247            Some(Some(value)) => Self::Value(value),
248            Some(None) => Self::Null,
249            None => Self::Undefined,
250        }
251    }
252}
253
254/// Utility trait for builder methods for optional values.
255/// This allows the caller to either pass in the value itself without wrapping it in `Some`,
256/// or to just pass in an Option if that is what they have, or set it back to undefined.
257pub trait IntoMaybeUndefined<T> {
258    fn into_maybe_undefined(self) -> MaybeUndefined<T>;
259}
260
261impl<T> IntoMaybeUndefined<T> for T {
262    fn into_maybe_undefined(self) -> MaybeUndefined<T> {
263        MaybeUndefined::Value(self)
264    }
265}
266
267impl<T> IntoMaybeUndefined<T> for Option<T> {
268    fn into_maybe_undefined(self) -> MaybeUndefined<T> {
269        match self {
270            Some(value) => MaybeUndefined::Value(value),
271            None => MaybeUndefined::Null,
272        }
273    }
274}
275
276impl<T> IntoMaybeUndefined<T> for MaybeUndefined<T> {
277    fn into_maybe_undefined(self) -> MaybeUndefined<T> {
278        self
279    }
280}
281
282impl IntoMaybeUndefined<String> for &str {
283    fn into_maybe_undefined(self) -> MaybeUndefined<String> {
284        MaybeUndefined::Value(self.into())
285    }
286}
287
288impl IntoMaybeUndefined<String> for &mut str {
289    fn into_maybe_undefined(self) -> MaybeUndefined<String> {
290        MaybeUndefined::Value(self.into())
291    }
292}
293
294impl IntoMaybeUndefined<String> for &String {
295    fn into_maybe_undefined(self) -> MaybeUndefined<String> {
296        MaybeUndefined::Value(self.into())
297    }
298}
299
300impl IntoMaybeUndefined<String> for Box<str> {
301    fn into_maybe_undefined(self) -> MaybeUndefined<String> {
302        MaybeUndefined::Value(self.into())
303    }
304}
305
306impl IntoMaybeUndefined<String> for Cow<'_, str> {
307    fn into_maybe_undefined(self) -> MaybeUndefined<String> {
308        MaybeUndefined::Value(self.into())
309    }
310}
311
312impl IntoMaybeUndefined<String> for Arc<str> {
313    fn into_maybe_undefined(self) -> MaybeUndefined<String> {
314        MaybeUndefined::Value(self.to_string())
315    }
316}
317
318impl<T: ?Sized + AsRef<OsStr>> IntoMaybeUndefined<PathBuf> for &T {
319    fn into_maybe_undefined(self) -> MaybeUndefined<PathBuf> {
320        MaybeUndefined::Value(self.into())
321    }
322}
323
324impl IntoMaybeUndefined<PathBuf> for Box<Path> {
325    fn into_maybe_undefined(self) -> MaybeUndefined<PathBuf> {
326        MaybeUndefined::Value(self.into())
327    }
328}
329
330impl IntoMaybeUndefined<PathBuf> for Cow<'_, Path> {
331    fn into_maybe_undefined(self) -> MaybeUndefined<PathBuf> {
332        MaybeUndefined::Value(self.into())
333    }
334}
335
336impl IntoMaybeUndefined<serde_json::Value> for &str {
337    fn into_maybe_undefined(self) -> MaybeUndefined<serde_json::Value> {
338        MaybeUndefined::Value(self.into())
339    }
340}
341
342impl IntoMaybeUndefined<serde_json::Value> for String {
343    fn into_maybe_undefined(self) -> MaybeUndefined<serde_json::Value> {
344        MaybeUndefined::Value(self.into())
345    }
346}
347
348impl IntoMaybeUndefined<serde_json::Value> for Cow<'_, str> {
349    fn into_maybe_undefined(self) -> MaybeUndefined<serde_json::Value> {
350        MaybeUndefined::Value(self.into())
351    }
352}
353
354#[cfg(test)]
355mod tests {
356    use serde::{Deserialize, Serialize};
357    use serde_json::{from_value, json, to_value};
358
359    use super::*;
360
361    #[test]
362    fn test_maybe_undefined_serde() {
363        #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
364        struct A {
365            #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
366            a: MaybeUndefined<i32>,
367        }
368
369        assert_eq!(to_value(MaybeUndefined::Value(100i32)).unwrap(), json!(100));
370
371        assert_eq!(
372            from_value::<MaybeUndefined<i32>>(json!(100)).unwrap(),
373            MaybeUndefined::Value(100)
374        );
375        assert_eq!(
376            from_value::<MaybeUndefined<i32>>(json!(null)).unwrap(),
377            MaybeUndefined::Null
378        );
379
380        assert_eq!(
381            to_value(&A {
382                a: MaybeUndefined::Value(100i32)
383            })
384            .unwrap(),
385            json!({"a": 100})
386        );
387
388        assert_eq!(
389            to_value(&A {
390                a: MaybeUndefined::Null,
391            })
392            .unwrap(),
393            json!({ "a": null })
394        );
395
396        assert_eq!(
397            to_value(&A {
398                a: MaybeUndefined::Undefined,
399            })
400            .unwrap(),
401            json!({})
402        );
403
404        assert_eq!(
405            from_value::<A>(json!({"a": 100})).unwrap(),
406            A {
407                a: MaybeUndefined::Value(100i32)
408            }
409        );
410
411        assert_eq!(
412            from_value::<A>(json!({ "a": null })).unwrap(),
413            A {
414                a: MaybeUndefined::Null
415            }
416        );
417
418        assert_eq!(
419            from_value::<A>(json!({})).unwrap(),
420            A {
421                a: MaybeUndefined::Undefined
422            }
423        );
424    }
425
426    #[test]
427    fn test_maybe_undefined_to_nested_option() {
428        assert_eq!(Option::<Option<i32>>::from(MaybeUndefined::Undefined), None);
429
430        assert_eq!(
431            Option::<Option<i32>>::from(MaybeUndefined::Null),
432            Some(None)
433        );
434
435        assert_eq!(
436            Option::<Option<i32>>::from(MaybeUndefined::Value(42)),
437            Some(Some(42))
438        );
439    }
440
441    #[test]
442    fn test_as_opt_ref() {
443        let value = MaybeUndefined::<String>::Undefined;
444        let r = value.as_opt_ref();
445        assert_eq!(r, None);
446
447        let value = MaybeUndefined::<String>::Null;
448        let r = value.as_opt_ref();
449        assert_eq!(r, Some(None));
450
451        let value = MaybeUndefined::<String>::Value("abc".to_string());
452        let r = value.as_opt_ref();
453        assert_eq!(r, Some(Some(&"abc".to_string())));
454    }
455
456    #[test]
457    fn test_as_opt_deref() {
458        let value = MaybeUndefined::<String>::Undefined;
459        let r = value.as_opt_deref();
460        assert_eq!(r, None);
461
462        let value = MaybeUndefined::<String>::Null;
463        let r = value.as_opt_deref();
464        assert_eq!(r, Some(None));
465
466        let value = MaybeUndefined::<String>::Value("abc".to_string());
467        let r = value.as_opt_deref();
468        assert_eq!(r, Some(Some("abc")));
469    }
470
471    #[test]
472    fn test_contains_value() {
473        let test = "abc";
474
475        let mut value: MaybeUndefined<String> = MaybeUndefined::Undefined;
476        assert!(!value.contains_value(&test));
477
478        value = MaybeUndefined::Null;
479        assert!(!value.contains_value(&test));
480
481        value = MaybeUndefined::Value("abc".to_string());
482        assert!(value.contains_value(&test));
483    }
484
485    #[test]
486    fn test_contains() {
487        let test = Some("abc");
488        let none: Option<&str> = None;
489
490        let mut value: MaybeUndefined<String> = MaybeUndefined::Undefined;
491        assert!(!value.contains(test.as_ref()));
492        assert!(!value.contains(none.as_ref()));
493
494        value = MaybeUndefined::Null;
495        assert!(!value.contains(test.as_ref()));
496        assert!(value.contains(none.as_ref()));
497
498        value = MaybeUndefined::Value("abc".to_string());
499        assert!(value.contains(test.as_ref()));
500        assert!(!value.contains(none.as_ref()));
501    }
502
503    #[test]
504    fn test_map_value() {
505        let mut value: MaybeUndefined<i32> = MaybeUndefined::Undefined;
506        assert_eq!(value.map_value(|v| v > 2), MaybeUndefined::Undefined);
507
508        value = MaybeUndefined::Null;
509        assert_eq!(value.map_value(|v| v > 2), MaybeUndefined::Null);
510
511        value = MaybeUndefined::Value(5);
512        assert_eq!(value.map_value(|v| v > 2), MaybeUndefined::Value(true));
513    }
514
515    #[test]
516    fn test_map() {
517        let mut value: MaybeUndefined<i32> = MaybeUndefined::Undefined;
518        assert_eq!(value.map(|v| Some(v.is_some())), MaybeUndefined::Undefined);
519
520        value = MaybeUndefined::Null;
521        assert_eq!(
522            value.map(|v| Some(v.is_some())),
523            MaybeUndefined::Value(false)
524        );
525
526        value = MaybeUndefined::Value(5);
527        assert_eq!(
528            value.map(|v| Some(v.is_some())),
529            MaybeUndefined::Value(true)
530        );
531    }
532
533    #[test]
534    fn test_transpose() {
535        let mut value: MaybeUndefined<Result<i32, &'static str>> = MaybeUndefined::Undefined;
536        assert_eq!(value.transpose(), Ok(MaybeUndefined::Undefined));
537
538        value = MaybeUndefined::Null;
539        assert_eq!(value.transpose(), Ok(MaybeUndefined::Null));
540
541        value = MaybeUndefined::Value(Ok(5));
542        assert_eq!(value.transpose(), Ok(MaybeUndefined::Value(5)));
543
544        value = MaybeUndefined::Value(Err("error"));
545        assert_eq!(value.transpose(), Err("error"));
546    }
547}