Skip to main content

agent_client_protocol_schema/
serde_util.rs

1//! Custom option-like field wrappers and builder helpers for serde.
2//!
3//! ## Types
4//!
5//! - [`MaybeUndefined<T>`] — three-state: undefined (key absent), null, or value.
6//! - [`SkipListener`] — [`serde_with::InspectError`] hook used by every
7//!   `VecSkipError` call site in the protocol types.
8//!
9//! ## Builder traits
10//!
11//! - [`IntoOption<T>`] — ergonomic conversion into `Option<T>` for builder methods.
12//! - [`IntoMaybeUndefined<T>`] — ergonomic conversion into `MaybeUndefined<T>` for builder methods.
13//!
14//! `MaybeUndefined` based on: <https://docs.rs/async-graphql/latest/src/async_graphql/types/maybe_undefined.rs.html>
15use std::{
16    borrow::Cow,
17    ffi::OsStr,
18    ops::Deref,
19    path::{Path, PathBuf},
20    sync::Arc,
21};
22
23use schemars::JsonSchema;
24use serde::{Deserialize, Deserializer, Serialize, Serializer};
25use serde_with::{DeserializeAs, de::DeserializeAsWrap};
26
27// ---- SkipListener ----
28
29/// Inspector passed to every `VecSkipError<_, SkipListener>` in the protocol
30/// types so that malformed list entries dropped during deserialization are
31/// surfaced to observability tooling rather than vanishing silently.
32///
33/// - With the `tracing` feature enabled, this is a zero-sized type whose
34///   [`InspectError`](serde_with::InspectError) implementation emits a
35///   [`tracing::warn!`] event on every skipped entry.
36/// - With the feature disabled (the default), it resolves to `()` — which
37///   `serde_with` ships with a no-op `InspectError` implementation — so call
38///   sites incur zero runtime cost.
39#[cfg(feature = "tracing")]
40#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
41#[non_exhaustive]
42pub struct SkipListener;
43
44#[cfg(feature = "tracing")]
45impl serde_with::InspectError for SkipListener {
46    fn inspect_error(error: impl serde::de::Error) {
47        tracing::warn!(
48            %error,
49            "skipped malformed list entry during deserialization",
50        );
51    }
52}
53
54/// Zero-cost stand-in for [`SkipListener`] when the `tracing` feature is
55/// disabled. Resolves to `()`, which `serde_with` already ships with a no-op
56/// `InspectError` implementation.
57#[cfg(not(feature = "tracing"))]
58pub type SkipListener = ();
59
60#[cfg(test)]
61mod skip_listener_tests {
62    use std::cell::Cell;
63
64    use serde::{Deserialize, Serialize};
65    use serde_json::json;
66    use serde_with::{DefaultOnError, VecSkipError, serde_as};
67
68    thread_local! {
69        static SKIP_COUNT: Cell<u32> = const { Cell::new(0) };
70    }
71
72    /// Test-only inspector that counts skipped entries.
73    struct CountingListener;
74
75    impl serde_with::InspectError for CountingListener {
76        fn inspect_error(_error: impl serde::de::Error) {
77            SKIP_COUNT.with(|c| c.set(c.get() + 1));
78        }
79    }
80
81    #[serde_as]
82    #[derive(Serialize, Deserialize, Debug, PartialEq)]
83    struct Wrapper {
84        #[serde_as(deserialize_as = "VecSkipError<_, CountingListener>")]
85        values: Vec<u32>,
86    }
87
88    #[test]
89    fn inspector_runs_for_each_skipped_entry() {
90        SKIP_COUNT.with(|c| c.set(0));
91
92        let input = json!({"values": [1, "oops", 2, {}, 3]});
93        let wrapper: Wrapper = serde_json::from_value(input).unwrap();
94
95        assert_eq!(wrapper.values, vec![1, 2, 3]);
96        assert_eq!(SKIP_COUNT.with(Cell::get), 2);
97    }
98
99    /// Mirrors the pattern applied to every required `Vec<T>` field in the
100    /// protocol: `DefaultOnError<VecSkipError<_, ...>>` + `#[serde(default)]`.
101    /// Element-level failures are skipped; any outer shape error (`null`, a
102    /// string, a map, etc.) collapses to `Default::default()` (i.e. `vec![]`).
103    #[serde_as]
104    #[derive(Deserialize, Debug, PartialEq)]
105    struct ResilientVec {
106        #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, CountingListener>>")]
107        #[serde(default)]
108        values: Vec<u32>,
109    }
110
111    #[test]
112    fn resilient_vec_tolerates_missing_null_and_wrong_type() {
113        // Missing field -> `#[serde(default)]` supplies `vec![]`.
114        let r: ResilientVec = serde_json::from_value(json!({})).unwrap();
115        assert_eq!(r.values, Vec::<u32>::new());
116
117        // Explicit null -> `DefaultOnError` swallows the type error.
118        let r: ResilientVec = serde_json::from_value(json!({"values": null})).unwrap();
119        assert_eq!(r.values, Vec::<u32>::new());
120
121        // Wrong outer type (string) -> `DefaultOnError` swallows.
122        let r: ResilientVec = serde_json::from_value(json!({"values": "oops"})).unwrap();
123        assert_eq!(r.values, Vec::<u32>::new());
124
125        // Wrong outer type (object) -> `DefaultOnError` swallows.
126        let r: ResilientVec = serde_json::from_value(json!({"values": {"k": 1}})).unwrap();
127        assert_eq!(r.values, Vec::<u32>::new());
128
129        // Valid array with element errors -> `VecSkipError` skips per-element.
130        SKIP_COUNT.with(|c| c.set(0));
131        let r: ResilientVec =
132            serde_json::from_value(json!({"values": [1, "oops", 2, {}, 3]})).unwrap();
133        assert_eq!(r.values, vec![1, 2, 3]);
134        assert_eq!(SKIP_COUNT.with(Cell::get), 2);
135    }
136
137    #[test]
138    fn resilient_vec_does_not_invoke_inspector_on_outer_failure() {
139        SKIP_COUNT.with(|c| c.set(0));
140
141        // Outer failures are swallowed silently by `DefaultOnError`; the
142        // inspector only sees per-element failures inside a valid array.
143        let _r: ResilientVec = serde_json::from_value(json!({"values": null})).unwrap();
144        let _r: ResilientVec = serde_json::from_value(json!({"values": "oops"})).unwrap();
145        let _r: ResilientVec = serde_json::from_value(json!({"values": {}})).unwrap();
146
147        assert_eq!(SKIP_COUNT.with(Cell::get), 0);
148    }
149
150    /// Mirrors the pattern applied to every optional `Option<Vec<T>>` field:
151    /// `DefaultOnError<Option<VecSkipError<_, ...>>>` + `#[serde(default)]`.
152    /// `null` becomes `None`; outer shape errors also collapse to `None`;
153    /// element-level failures are skipped inside the array.
154    #[serde_as]
155    #[derive(Deserialize, Debug, PartialEq)]
156    struct ResilientOptionVec {
157        #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, CountingListener>>>")]
158        #[serde(default)]
159        values: Option<Vec<u32>>,
160    }
161
162    #[test]
163    fn resilient_option_vec_tolerates_missing_null_and_wrong_type() {
164        // Missing field -> `None`.
165        let r: ResilientOptionVec = serde_json::from_value(json!({})).unwrap();
166        assert_eq!(r.values, None);
167
168        // Explicit null -> `None`.
169        let r: ResilientOptionVec = serde_json::from_value(json!({"values": null})).unwrap();
170        assert_eq!(r.values, None);
171
172        // Empty array -> `Some(vec![])`.
173        let r: ResilientOptionVec = serde_json::from_value(json!({"values": []})).unwrap();
174        assert_eq!(r.values, Some(Vec::<u32>::new()));
175
176        // Valid array -> `Some(vec)`.
177        let r: ResilientOptionVec = serde_json::from_value(json!({"values": [1, 2, 3]})).unwrap();
178        assert_eq!(r.values, Some(vec![1, 2, 3]));
179
180        // Wrong outer type (string) -> `DefaultOnError` collapses to `None`.
181        let r: ResilientOptionVec = serde_json::from_value(json!({"values": "oops"})).unwrap();
182        assert_eq!(r.values, None);
183
184        // Wrong outer type (object) -> `DefaultOnError` collapses to `None`.
185        let r: ResilientOptionVec = serde_json::from_value(json!({"values": {"k": 1}})).unwrap();
186        assert_eq!(r.values, None);
187
188        // Valid array with element errors -> `VecSkipError` skips per-element.
189        SKIP_COUNT.with(|c| c.set(0));
190        let r: ResilientOptionVec =
191            serde_json::from_value(json!({"values": [1, "oops", 2, {}, 3]})).unwrap();
192        assert_eq!(r.values, Some(vec![1, 2, 3]));
193        assert_eq!(SKIP_COUNT.with(Cell::get), 2);
194    }
195}
196
197// ---- IntoOption ----
198
199/// Utility trait for builder methods for optional values.
200/// This allows the caller to either pass in the value itself without wrapping it in `Some`,
201/// or to just pass in an Option if that is what they have.
202pub trait IntoOption<T> {
203    fn into_option(self) -> Option<T>;
204}
205
206impl<T> IntoOption<T> for Option<T> {
207    fn into_option(self) -> Option<T> {
208        self
209    }
210}
211
212impl<T> IntoOption<T> for T {
213    fn into_option(self) -> Option<T> {
214        Some(self)
215    }
216}
217
218impl IntoOption<String> for &str {
219    fn into_option(self) -> Option<String> {
220        Some(self.into())
221    }
222}
223
224impl IntoOption<String> for &mut str {
225    fn into_option(self) -> Option<String> {
226        Some(self.into())
227    }
228}
229
230impl IntoOption<String> for &String {
231    fn into_option(self) -> Option<String> {
232        Some(self.into())
233    }
234}
235
236impl IntoOption<String> for Box<str> {
237    fn into_option(self) -> Option<String> {
238        Some(self.into())
239    }
240}
241
242impl IntoOption<String> for Cow<'_, str> {
243    fn into_option(self) -> Option<String> {
244        Some(self.into())
245    }
246}
247
248impl IntoOption<String> for Arc<str> {
249    fn into_option(self) -> Option<String> {
250        Some(self.to_string())
251    }
252}
253
254impl<T: ?Sized + AsRef<OsStr>> IntoOption<PathBuf> for &T {
255    fn into_option(self) -> Option<PathBuf> {
256        Some(self.into())
257    }
258}
259
260impl IntoOption<PathBuf> for Box<Path> {
261    fn into_option(self) -> Option<PathBuf> {
262        Some(self.into())
263    }
264}
265
266impl IntoOption<PathBuf> for Cow<'_, Path> {
267    fn into_option(self) -> Option<PathBuf> {
268        Some(self.into())
269    }
270}
271
272impl IntoOption<serde_json::Value> for &str {
273    fn into_option(self) -> Option<serde_json::Value> {
274        Some(self.into())
275    }
276}
277
278impl IntoOption<serde_json::Value> for String {
279    fn into_option(self) -> Option<serde_json::Value> {
280        Some(self.into())
281    }
282}
283
284impl IntoOption<serde_json::Value> for Cow<'_, str> {
285    fn into_option(self) -> Option<serde_json::Value> {
286        Some(self.into())
287    }
288}
289
290// ---- MaybeUndefined ----
291
292/// Similar to `Option`, but it has three states, `undefined`, `null` and `x`.
293///
294/// When using with Serde, you will likely want to skip serialization of `undefined`
295/// and add a `default` for deserialization.
296///
297/// # Example
298///
299/// ```rust
300/// use agent_client_protocol_schema::MaybeUndefined;
301/// use serde::{Serialize, Deserialize};
302///
303/// #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
304/// struct A {
305///     #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
306///     a: MaybeUndefined<i32>,
307/// }
308/// ```
309#[derive(Copy, Clone, Default, PartialEq, PartialOrd, Eq, Ord, Debug, Hash, JsonSchema)]
310#[schemars(with = "Option<Option<T>>", inline)]
311#[expect(clippy::exhaustive_enums)]
312pub enum MaybeUndefined<T> {
313    #[default]
314    Undefined,
315    Null,
316    Value(T),
317}
318
319impl<T> MaybeUndefined<T> {
320    /// Returns true if the `MaybeUndefined<T>` is undefined.
321    #[inline]
322    pub const fn is_undefined(&self) -> bool {
323        matches!(self, MaybeUndefined::Undefined)
324    }
325
326    /// Returns true if the `MaybeUndefined<T>` is null.
327    #[inline]
328    pub const fn is_null(&self) -> bool {
329        matches!(self, MaybeUndefined::Null)
330    }
331
332    /// Returns true if the `MaybeUndefined<T>` contains value.
333    #[inline]
334    pub const fn is_value(&self) -> bool {
335        matches!(self, MaybeUndefined::Value(_))
336    }
337
338    /// Borrow the value, returns `None` if the `MaybeUndefined<T>` is
339    /// `undefined` or `null`, otherwise returns `Some(T)`.
340    #[inline]
341    pub const fn value(&self) -> Option<&T> {
342        match self {
343            MaybeUndefined::Value(value) => Some(value),
344            _ => None,
345        }
346    }
347
348    /// Converts the `MaybeUndefined<T>` to `Option<T>`.
349    #[inline]
350    pub fn take(self) -> Option<T> {
351        match self {
352            MaybeUndefined::Value(value) => Some(value),
353            _ => None,
354        }
355    }
356
357    /// Converts the `MaybeUndefined<T>` to `Option<Option<T>>`.
358    #[inline]
359    pub const fn as_opt_ref(&self) -> Option<Option<&T>> {
360        match self {
361            MaybeUndefined::Undefined => None,
362            MaybeUndefined::Null => Some(None),
363            MaybeUndefined::Value(value) => Some(Some(value)),
364        }
365    }
366
367    /// Converts the `MaybeUndefined<T>` to `Option<Option<&U>>`.
368    #[inline]
369    pub fn as_opt_deref<U>(&self) -> Option<Option<&U>>
370    where
371        U: ?Sized,
372        T: Deref<Target = U>,
373    {
374        match self {
375            MaybeUndefined::Undefined => None,
376            MaybeUndefined::Null => Some(None),
377            MaybeUndefined::Value(value) => Some(Some(&**value)),
378        }
379    }
380
381    /// Returns `true` if the `MaybeUndefined<T>` contains the given value.
382    #[inline]
383    pub fn contains_value<U>(&self, x: &U) -> bool
384    where
385        U: PartialEq<T>,
386    {
387        match self {
388            MaybeUndefined::Value(y) => x == y,
389            _ => false,
390        }
391    }
392
393    /// Returns `true` if the `MaybeUndefined<T>` contains the given nullable
394    /// value.
395    #[inline]
396    pub fn contains<U>(&self, x: Option<&U>) -> bool
397    where
398        U: PartialEq<T>,
399    {
400        match self {
401            MaybeUndefined::Value(y) => matches!(x, Some(v) if v == y),
402            MaybeUndefined::Null => x.is_none(),
403            MaybeUndefined::Undefined => false,
404        }
405    }
406
407    /// Maps a `MaybeUndefined<T>` to `MaybeUndefined<U>` by applying a function
408    /// to the contained nullable value
409    #[inline]
410    pub fn map<U, F: FnOnce(Option<T>) -> Option<U>>(self, f: F) -> MaybeUndefined<U> {
411        match self {
412            MaybeUndefined::Value(v) => match f(Some(v)) {
413                Some(v) => MaybeUndefined::Value(v),
414                None => MaybeUndefined::Null,
415            },
416            MaybeUndefined::Null => match f(None) {
417                Some(v) => MaybeUndefined::Value(v),
418                None => MaybeUndefined::Null,
419            },
420            MaybeUndefined::Undefined => MaybeUndefined::Undefined,
421        }
422    }
423
424    /// Maps a `MaybeUndefined<T>` to `MaybeUndefined<U>` by applying a function
425    /// to the contained value
426    #[inline]
427    pub fn map_value<U, F: FnOnce(T) -> U>(self, f: F) -> MaybeUndefined<U> {
428        match self {
429            MaybeUndefined::Value(v) => MaybeUndefined::Value(f(v)),
430            MaybeUndefined::Null => MaybeUndefined::Null,
431            MaybeUndefined::Undefined => MaybeUndefined::Undefined,
432        }
433    }
434
435    /// Update `value` if the `MaybeUndefined<T>` is not undefined.
436    ///
437    /// # Example
438    ///
439    /// ```rust
440    /// use agent_client_protocol_schema::MaybeUndefined;
441    ///
442    /// let mut value = None;
443    ///
444    /// MaybeUndefined::Value(10i32).update_to(&mut value);
445    /// assert_eq!(value, Some(10));
446    ///
447    /// MaybeUndefined::Undefined.update_to(&mut value);
448    /// assert_eq!(value, Some(10));
449    ///
450    /// MaybeUndefined::Null.update_to(&mut value);
451    /// assert_eq!(value, None);
452    /// ```
453    pub fn update_to(self, value: &mut Option<T>) {
454        match self {
455            MaybeUndefined::Value(new) => *value = Some(new),
456            MaybeUndefined::Null => *value = None,
457            MaybeUndefined::Undefined => {}
458        }
459    }
460}
461
462impl<T, E> MaybeUndefined<Result<T, E>> {
463    /// Transposes a `MaybeUndefined` of a [`Result`] into a [`Result`] of a
464    /// `MaybeUndefined`.
465    ///
466    /// [`MaybeUndefined::Undefined`] will be mapped to
467    /// [`Ok`]`(`[`MaybeUndefined::Undefined`]`)`. [`MaybeUndefined::Null`]
468    /// will be mapped to [`Ok`]`(`[`MaybeUndefined::Null`]`)`.
469    /// [`MaybeUndefined::Value`]`(`[`Ok`]`(_))` and
470    /// [`MaybeUndefined::Value`]`(`[`Err`]`(_))` will be mapped to
471    /// [`Ok`]`(`[`MaybeUndefined::Value`]`(_))` and [`Err`]`(_)`.
472    ///
473    /// # Errors
474    ///
475    /// Returns an error if the input is [`MaybeUndefined::Value`]`(`[`Err`]`(_))`.
476    #[inline]
477    pub fn transpose(self) -> Result<MaybeUndefined<T>, E> {
478        match self {
479            MaybeUndefined::Undefined => Ok(MaybeUndefined::Undefined),
480            MaybeUndefined::Null => Ok(MaybeUndefined::Null),
481            MaybeUndefined::Value(Ok(v)) => Ok(MaybeUndefined::Value(v)),
482            MaybeUndefined::Value(Err(e)) => Err(e),
483        }
484    }
485}
486
487impl<T: Serialize> Serialize for MaybeUndefined<T> {
488    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
489        match self {
490            MaybeUndefined::Value(value) => value.serialize(serializer),
491            MaybeUndefined::Null => serializer.serialize_none(),
492            MaybeUndefined::Undefined => serializer.serialize_unit(),
493        }
494    }
495}
496
497impl<'de, T> Deserialize<'de> for MaybeUndefined<T>
498where
499    T: Deserialize<'de>,
500{
501    fn deserialize<D>(deserializer: D) -> Result<MaybeUndefined<T>, D::Error>
502    where
503        D: Deserializer<'de>,
504    {
505        Option::<T>::deserialize(deserializer).map(|value| match value {
506            Some(value) => MaybeUndefined::Value(value),
507            None => MaybeUndefined::Null,
508        })
509    }
510}
511
512impl<T> From<MaybeUndefined<T>> for Option<Option<T>> {
513    fn from(maybe_undefined: MaybeUndefined<T>) -> Self {
514        match maybe_undefined {
515            MaybeUndefined::Undefined => None,
516            MaybeUndefined::Null => Some(None),
517            MaybeUndefined::Value(value) => Some(Some(value)),
518        }
519    }
520}
521
522impl<T> From<Option<Option<T>>> for MaybeUndefined<T> {
523    fn from(value: Option<Option<T>>) -> Self {
524        match value {
525            Some(Some(value)) => Self::Value(value),
526            Some(None) => Self::Null,
527            None => Self::Undefined,
528        }
529    }
530}
531
532impl<'de, T, TAs> DeserializeAs<'de, MaybeUndefined<T>> for MaybeUndefined<TAs>
533where
534    TAs: DeserializeAs<'de, T>,
535{
536    fn deserialize_as<D>(deserializer: D) -> Result<MaybeUndefined<T>, D::Error>
537    where
538        D: Deserializer<'de>,
539    {
540        Option::<DeserializeAsWrap<T, TAs>>::deserialize(deserializer).map(|value| match value {
541            Some(value) => MaybeUndefined::Value(value.into_inner()),
542            None => MaybeUndefined::Null,
543        })
544    }
545}
546
547/// Utility trait for builder methods for optional values.
548/// This allows the caller to either pass in the value itself without wrapping it in `Some`,
549/// or to just pass in an Option if that is what they have, or set it back to undefined.
550pub trait IntoMaybeUndefined<T> {
551    fn into_maybe_undefined(self) -> MaybeUndefined<T>;
552}
553
554impl<T> IntoMaybeUndefined<T> for T {
555    fn into_maybe_undefined(self) -> MaybeUndefined<T> {
556        MaybeUndefined::Value(self)
557    }
558}
559
560impl<T> IntoMaybeUndefined<T> for Option<T> {
561    fn into_maybe_undefined(self) -> MaybeUndefined<T> {
562        match self {
563            Some(value) => MaybeUndefined::Value(value),
564            None => MaybeUndefined::Null,
565        }
566    }
567}
568
569impl<T> IntoMaybeUndefined<T> for MaybeUndefined<T> {
570    fn into_maybe_undefined(self) -> MaybeUndefined<T> {
571        self
572    }
573}
574
575impl IntoMaybeUndefined<String> for &str {
576    fn into_maybe_undefined(self) -> MaybeUndefined<String> {
577        MaybeUndefined::Value(self.into())
578    }
579}
580
581impl IntoMaybeUndefined<String> for &mut str {
582    fn into_maybe_undefined(self) -> MaybeUndefined<String> {
583        MaybeUndefined::Value(self.into())
584    }
585}
586
587impl IntoMaybeUndefined<String> for &String {
588    fn into_maybe_undefined(self) -> MaybeUndefined<String> {
589        MaybeUndefined::Value(self.into())
590    }
591}
592
593impl IntoMaybeUndefined<String> for Box<str> {
594    fn into_maybe_undefined(self) -> MaybeUndefined<String> {
595        MaybeUndefined::Value(self.into())
596    }
597}
598
599impl IntoMaybeUndefined<String> for Cow<'_, str> {
600    fn into_maybe_undefined(self) -> MaybeUndefined<String> {
601        MaybeUndefined::Value(self.into())
602    }
603}
604
605impl IntoMaybeUndefined<String> for Arc<str> {
606    fn into_maybe_undefined(self) -> MaybeUndefined<String> {
607        MaybeUndefined::Value(self.to_string())
608    }
609}
610
611impl<T: ?Sized + AsRef<OsStr>> IntoMaybeUndefined<PathBuf> for &T {
612    fn into_maybe_undefined(self) -> MaybeUndefined<PathBuf> {
613        MaybeUndefined::Value(self.into())
614    }
615}
616
617impl IntoMaybeUndefined<PathBuf> for Box<Path> {
618    fn into_maybe_undefined(self) -> MaybeUndefined<PathBuf> {
619        MaybeUndefined::Value(self.into())
620    }
621}
622
623impl IntoMaybeUndefined<PathBuf> for Cow<'_, Path> {
624    fn into_maybe_undefined(self) -> MaybeUndefined<PathBuf> {
625        MaybeUndefined::Value(self.into())
626    }
627}
628
629impl IntoMaybeUndefined<serde_json::Value> for &str {
630    fn into_maybe_undefined(self) -> MaybeUndefined<serde_json::Value> {
631        MaybeUndefined::Value(self.into())
632    }
633}
634
635impl IntoMaybeUndefined<serde_json::Value> for String {
636    fn into_maybe_undefined(self) -> MaybeUndefined<serde_json::Value> {
637        MaybeUndefined::Value(self.into())
638    }
639}
640
641impl IntoMaybeUndefined<serde_json::Value> for Cow<'_, str> {
642    fn into_maybe_undefined(self) -> MaybeUndefined<serde_json::Value> {
643        MaybeUndefined::Value(self.into())
644    }
645}
646
647#[cfg(test)]
648mod tests {
649    use serde::{Deserialize, Serialize};
650    use serde_json::{from_value, json, to_value};
651
652    use super::*;
653
654    #[test]
655    fn test_maybe_undefined_serde() {
656        #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
657        struct A {
658            #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
659            a: MaybeUndefined<i32>,
660        }
661
662        assert_eq!(to_value(MaybeUndefined::Value(100i32)).unwrap(), json!(100));
663
664        assert_eq!(
665            from_value::<MaybeUndefined<i32>>(json!(100)).unwrap(),
666            MaybeUndefined::Value(100)
667        );
668        assert_eq!(
669            from_value::<MaybeUndefined<i32>>(json!(null)).unwrap(),
670            MaybeUndefined::Null
671        );
672
673        assert_eq!(
674            to_value(&A {
675                a: MaybeUndefined::Value(100i32)
676            })
677            .unwrap(),
678            json!({"a": 100})
679        );
680
681        assert_eq!(
682            to_value(&A {
683                a: MaybeUndefined::Null,
684            })
685            .unwrap(),
686            json!({ "a": null })
687        );
688
689        assert_eq!(
690            to_value(&A {
691                a: MaybeUndefined::Undefined,
692            })
693            .unwrap(),
694            json!({})
695        );
696
697        assert_eq!(
698            from_value::<A>(json!({"a": 100})).unwrap(),
699            A {
700                a: MaybeUndefined::Value(100i32)
701            }
702        );
703
704        assert_eq!(
705            from_value::<A>(json!({ "a": null })).unwrap(),
706            A {
707                a: MaybeUndefined::Null
708            }
709        );
710
711        assert_eq!(
712            from_value::<A>(json!({})).unwrap(),
713            A {
714                a: MaybeUndefined::Undefined
715            }
716        );
717    }
718
719    #[test]
720    fn test_maybe_undefined_to_nested_option() {
721        assert_eq!(Option::<Option<i32>>::from(MaybeUndefined::Undefined), None);
722
723        assert_eq!(
724            Option::<Option<i32>>::from(MaybeUndefined::Null),
725            Some(None)
726        );
727
728        assert_eq!(
729            Option::<Option<i32>>::from(MaybeUndefined::Value(42)),
730            Some(Some(42))
731        );
732    }
733
734    #[test]
735    fn test_as_opt_ref() {
736        let value = MaybeUndefined::<String>::Undefined;
737        let r = value.as_opt_ref();
738        assert_eq!(r, None);
739
740        let value = MaybeUndefined::<String>::Null;
741        let r = value.as_opt_ref();
742        assert_eq!(r, Some(None));
743
744        let value = MaybeUndefined::<String>::Value("abc".to_string());
745        let r = value.as_opt_ref();
746        assert_eq!(r, Some(Some(&"abc".to_string())));
747    }
748
749    #[test]
750    fn test_as_opt_deref() {
751        let value = MaybeUndefined::<String>::Undefined;
752        let r = value.as_opt_deref();
753        assert_eq!(r, None);
754
755        let value = MaybeUndefined::<String>::Null;
756        let r = value.as_opt_deref();
757        assert_eq!(r, Some(None));
758
759        let value = MaybeUndefined::<String>::Value("abc".to_string());
760        let r = value.as_opt_deref();
761        assert_eq!(r, Some(Some("abc")));
762    }
763
764    #[test]
765    fn test_contains_value() {
766        let test = "abc";
767
768        let mut value: MaybeUndefined<String> = MaybeUndefined::Undefined;
769        assert!(!value.contains_value(&test));
770
771        value = MaybeUndefined::Null;
772        assert!(!value.contains_value(&test));
773
774        value = MaybeUndefined::Value("abc".to_string());
775        assert!(value.contains_value(&test));
776    }
777
778    #[test]
779    fn test_contains() {
780        let test = Some("abc");
781        let none: Option<&str> = None;
782
783        let mut value: MaybeUndefined<String> = MaybeUndefined::Undefined;
784        assert!(!value.contains(test.as_ref()));
785        assert!(!value.contains(none.as_ref()));
786
787        value = MaybeUndefined::Null;
788        assert!(!value.contains(test.as_ref()));
789        assert!(value.contains(none.as_ref()));
790
791        value = MaybeUndefined::Value("abc".to_string());
792        assert!(value.contains(test.as_ref()));
793        assert!(!value.contains(none.as_ref()));
794    }
795
796    #[test]
797    fn test_map_value() {
798        let mut value: MaybeUndefined<i32> = MaybeUndefined::Undefined;
799        assert_eq!(value.map_value(|v| v > 2), MaybeUndefined::Undefined);
800
801        value = MaybeUndefined::Null;
802        assert_eq!(value.map_value(|v| v > 2), MaybeUndefined::Null);
803
804        value = MaybeUndefined::Value(5);
805        assert_eq!(value.map_value(|v| v > 2), MaybeUndefined::Value(true));
806    }
807
808    #[test]
809    fn test_map() {
810        let mut value: MaybeUndefined<i32> = MaybeUndefined::Undefined;
811        assert_eq!(value.map(|v| Some(v.is_some())), MaybeUndefined::Undefined);
812
813        value = MaybeUndefined::Null;
814        assert_eq!(
815            value.map(|v| Some(v.is_some())),
816            MaybeUndefined::Value(false)
817        );
818
819        value = MaybeUndefined::Value(5);
820        assert_eq!(
821            value.map(|v| Some(v.is_some())),
822            MaybeUndefined::Value(true)
823        );
824    }
825
826    #[test]
827    fn test_transpose() {
828        let mut value: MaybeUndefined<Result<i32, &'static str>> = MaybeUndefined::Undefined;
829        assert_eq!(value.transpose(), Ok(MaybeUndefined::Undefined));
830
831        value = MaybeUndefined::Null;
832        assert_eq!(value.transpose(), Ok(MaybeUndefined::Null));
833
834        value = MaybeUndefined::Value(Ok(5));
835        assert_eq!(value.transpose(), Ok(MaybeUndefined::Value(5)));
836
837        value = MaybeUndefined::Value(Err("error"));
838        assert_eq!(value.transpose(), Err("error"));
839    }
840}