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//! - [`RequiredNullable<T>`] — required-but-nullable: key must be present, value may be null.
7//! - [`SkipListener`] — [`serde_with::InspectError`] hook used by every
8//!   `VecSkipError` call site in the protocol types.
9//!
10//! ## Builder traits
11//!
12//! - [`IntoOption<T>`] — ergonomic conversion into `Option<T>` for builder methods.
13//! - [`IntoMaybeUndefined<T>`] — ergonomic conversion into `MaybeUndefined<T>` for builder methods.
14//!
15//! `MaybeUndefined` based on: <https://docs.rs/async-graphql/latest/src/async_graphql/types/maybe_undefined.rs.html>
16use std::{
17    borrow::Cow,
18    ffi::OsStr,
19    ops::Deref,
20    path::{Path, PathBuf},
21    sync::Arc,
22};
23
24use schemars::JsonSchema;
25use serde::{Deserialize, Deserializer, Serialize, Serializer};
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#[allow(missing_docs)]
310#[derive(Copy, Clone, Default, PartialEq, PartialOrd, Eq, Ord, Debug, Hash, JsonSchema)]
311#[schemars(with = "Option<Option<T>>", inline)]
312#[expect(clippy::exhaustive_enums)]
313pub enum MaybeUndefined<T> {
314    #[default]
315    Undefined,
316    Null,
317    Value(T),
318}
319
320impl<T> MaybeUndefined<T> {
321    /// Returns true if the `MaybeUndefined<T>` is undefined.
322    #[inline]
323    pub const fn is_undefined(&self) -> bool {
324        matches!(self, MaybeUndefined::Undefined)
325    }
326
327    /// Returns true if the `MaybeUndefined<T>` is null.
328    #[inline]
329    pub const fn is_null(&self) -> bool {
330        matches!(self, MaybeUndefined::Null)
331    }
332
333    /// Returns true if the `MaybeUndefined<T>` contains value.
334    #[inline]
335    pub const fn is_value(&self) -> bool {
336        matches!(self, MaybeUndefined::Value(_))
337    }
338
339    /// Borrow the value, returns `None` if the `MaybeUndefined<T>` is
340    /// `undefined` or `null`, otherwise returns `Some(T)`.
341    #[inline]
342    pub const fn value(&self) -> Option<&T> {
343        match self {
344            MaybeUndefined::Value(value) => Some(value),
345            _ => None,
346        }
347    }
348
349    /// Converts the `MaybeUndefined<T>` to `Option<T>`.
350    #[inline]
351    pub fn take(self) -> Option<T> {
352        match self {
353            MaybeUndefined::Value(value) => Some(value),
354            _ => None,
355        }
356    }
357
358    /// Converts the `MaybeUndefined<T>` to `Option<Option<T>>`.
359    #[inline]
360    pub const fn as_opt_ref(&self) -> Option<Option<&T>> {
361        match self {
362            MaybeUndefined::Undefined => None,
363            MaybeUndefined::Null => Some(None),
364            MaybeUndefined::Value(value) => Some(Some(value)),
365        }
366    }
367
368    /// Converts the `MaybeUndefined<T>` to `Option<Option<&U>>`.
369    #[inline]
370    pub fn as_opt_deref<U>(&self) -> Option<Option<&U>>
371    where
372        U: ?Sized,
373        T: Deref<Target = U>,
374    {
375        match self {
376            MaybeUndefined::Undefined => None,
377            MaybeUndefined::Null => Some(None),
378            MaybeUndefined::Value(value) => Some(Some(&**value)),
379        }
380    }
381
382    /// Returns `true` if the `MaybeUndefined<T>` contains the given value.
383    #[inline]
384    pub fn contains_value<U>(&self, x: &U) -> bool
385    where
386        U: PartialEq<T>,
387    {
388        match self {
389            MaybeUndefined::Value(y) => x == y,
390            _ => false,
391        }
392    }
393
394    /// Returns `true` if the `MaybeUndefined<T>` contains the given nullable
395    /// value.
396    #[inline]
397    pub fn contains<U>(&self, x: Option<&U>) -> bool
398    where
399        U: PartialEq<T>,
400    {
401        match self {
402            MaybeUndefined::Value(y) => matches!(x, Some(v) if v == y),
403            MaybeUndefined::Null => x.is_none(),
404            MaybeUndefined::Undefined => false,
405        }
406    }
407
408    /// Maps a `MaybeUndefined<T>` to `MaybeUndefined<U>` by applying a function
409    /// to the contained nullable value
410    #[inline]
411    pub fn map<U, F: FnOnce(Option<T>) -> Option<U>>(self, f: F) -> MaybeUndefined<U> {
412        match self {
413            MaybeUndefined::Value(v) => match f(Some(v)) {
414                Some(v) => MaybeUndefined::Value(v),
415                None => MaybeUndefined::Null,
416            },
417            MaybeUndefined::Null => match f(None) {
418                Some(v) => MaybeUndefined::Value(v),
419                None => MaybeUndefined::Null,
420            },
421            MaybeUndefined::Undefined => MaybeUndefined::Undefined,
422        }
423    }
424
425    /// Maps a `MaybeUndefined<T>` to `MaybeUndefined<U>` by applying a function
426    /// to the contained value
427    #[inline]
428    pub fn map_value<U, F: FnOnce(T) -> U>(self, f: F) -> MaybeUndefined<U> {
429        match self {
430            MaybeUndefined::Value(v) => MaybeUndefined::Value(f(v)),
431            MaybeUndefined::Null => MaybeUndefined::Null,
432            MaybeUndefined::Undefined => MaybeUndefined::Undefined,
433        }
434    }
435
436    /// Update `value` if the `MaybeUndefined<T>` is not undefined.
437    ///
438    /// # Example
439    ///
440    /// ```rust
441    /// use agent_client_protocol_schema::MaybeUndefined;
442    ///
443    /// let mut value = None;
444    ///
445    /// MaybeUndefined::Value(10i32).update_to(&mut value);
446    /// assert_eq!(value, Some(10));
447    ///
448    /// MaybeUndefined::Undefined.update_to(&mut value);
449    /// assert_eq!(value, Some(10));
450    ///
451    /// MaybeUndefined::Null.update_to(&mut value);
452    /// assert_eq!(value, None);
453    /// ```
454    pub fn update_to(self, value: &mut Option<T>) {
455        match self {
456            MaybeUndefined::Value(new) => *value = Some(new),
457            MaybeUndefined::Null => *value = None,
458            MaybeUndefined::Undefined => {}
459        }
460    }
461}
462
463impl<T, E> MaybeUndefined<Result<T, E>> {
464    /// Transposes a `MaybeUndefined` of a [`Result`] into a [`Result`] of a
465    /// `MaybeUndefined`.
466    ///
467    /// [`MaybeUndefined::Undefined`] will be mapped to
468    /// [`Ok`]`(`[`MaybeUndefined::Undefined`]`)`. [`MaybeUndefined::Null`]
469    /// will be mapped to [`Ok`]`(`[`MaybeUndefined::Null`]`)`.
470    /// [`MaybeUndefined::Value`]`(`[`Ok`]`(_))` and
471    /// [`MaybeUndefined::Value`]`(`[`Err`]`(_))` will be mapped to
472    /// [`Ok`]`(`[`MaybeUndefined::Value`]`(_))` and [`Err`]`(_)`.
473    ///
474    /// # Errors
475    ///
476    /// Returns an error if the input is [`MaybeUndefined::Value`]`(`[`Err`]`(_))`.
477    #[inline]
478    pub fn transpose(self) -> Result<MaybeUndefined<T>, E> {
479        match self {
480            MaybeUndefined::Undefined => Ok(MaybeUndefined::Undefined),
481            MaybeUndefined::Null => Ok(MaybeUndefined::Null),
482            MaybeUndefined::Value(Ok(v)) => Ok(MaybeUndefined::Value(v)),
483            MaybeUndefined::Value(Err(e)) => Err(e),
484        }
485    }
486}
487
488impl<T: Serialize> Serialize for MaybeUndefined<T> {
489    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
490        match self {
491            MaybeUndefined::Value(value) => value.serialize(serializer),
492            MaybeUndefined::Null => serializer.serialize_none(),
493            MaybeUndefined::Undefined => serializer.serialize_unit(),
494        }
495    }
496}
497
498impl<'de, T> Deserialize<'de> for MaybeUndefined<T>
499where
500    T: Deserialize<'de>,
501{
502    fn deserialize<D>(deserializer: D) -> Result<MaybeUndefined<T>, D::Error>
503    where
504        D: Deserializer<'de>,
505    {
506        Option::<T>::deserialize(deserializer).map(|value| match value {
507            Some(value) => MaybeUndefined::Value(value),
508            None => MaybeUndefined::Null,
509        })
510    }
511}
512
513impl<T> From<MaybeUndefined<T>> for Option<Option<T>> {
514    fn from(maybe_undefined: MaybeUndefined<T>) -> Self {
515        match maybe_undefined {
516            MaybeUndefined::Undefined => None,
517            MaybeUndefined::Null => Some(None),
518            MaybeUndefined::Value(value) => Some(Some(value)),
519        }
520    }
521}
522
523impl<T> From<Option<Option<T>>> for MaybeUndefined<T> {
524    fn from(value: Option<Option<T>>) -> Self {
525        match value {
526            Some(Some(value)) => Self::Value(value),
527            Some(None) => Self::Null,
528            None => Self::Undefined,
529        }
530    }
531}
532
533/// Utility trait for builder methods for optional values.
534/// This allows the caller to either pass in the value itself without wrapping it in `Some`,
535/// or to just pass in an Option if that is what they have, or set it back to undefined.
536pub trait IntoMaybeUndefined<T> {
537    fn into_maybe_undefined(self) -> MaybeUndefined<T>;
538}
539
540impl<T> IntoMaybeUndefined<T> for T {
541    fn into_maybe_undefined(self) -> MaybeUndefined<T> {
542        MaybeUndefined::Value(self)
543    }
544}
545
546impl<T> IntoMaybeUndefined<T> for Option<T> {
547    fn into_maybe_undefined(self) -> MaybeUndefined<T> {
548        match self {
549            Some(value) => MaybeUndefined::Value(value),
550            None => MaybeUndefined::Null,
551        }
552    }
553}
554
555impl<T> IntoMaybeUndefined<T> for MaybeUndefined<T> {
556    fn into_maybe_undefined(self) -> MaybeUndefined<T> {
557        self
558    }
559}
560
561impl IntoMaybeUndefined<String> for &str {
562    fn into_maybe_undefined(self) -> MaybeUndefined<String> {
563        MaybeUndefined::Value(self.into())
564    }
565}
566
567impl IntoMaybeUndefined<String> for &mut str {
568    fn into_maybe_undefined(self) -> MaybeUndefined<String> {
569        MaybeUndefined::Value(self.into())
570    }
571}
572
573impl IntoMaybeUndefined<String> for &String {
574    fn into_maybe_undefined(self) -> MaybeUndefined<String> {
575        MaybeUndefined::Value(self.into())
576    }
577}
578
579impl IntoMaybeUndefined<String> for Box<str> {
580    fn into_maybe_undefined(self) -> MaybeUndefined<String> {
581        MaybeUndefined::Value(self.into())
582    }
583}
584
585impl IntoMaybeUndefined<String> for Cow<'_, str> {
586    fn into_maybe_undefined(self) -> MaybeUndefined<String> {
587        MaybeUndefined::Value(self.into())
588    }
589}
590
591impl IntoMaybeUndefined<String> for Arc<str> {
592    fn into_maybe_undefined(self) -> MaybeUndefined<String> {
593        MaybeUndefined::Value(self.to_string())
594    }
595}
596
597impl<T: ?Sized + AsRef<OsStr>> IntoMaybeUndefined<PathBuf> for &T {
598    fn into_maybe_undefined(self) -> MaybeUndefined<PathBuf> {
599        MaybeUndefined::Value(self.into())
600    }
601}
602
603impl IntoMaybeUndefined<PathBuf> for Box<Path> {
604    fn into_maybe_undefined(self) -> MaybeUndefined<PathBuf> {
605        MaybeUndefined::Value(self.into())
606    }
607}
608
609impl IntoMaybeUndefined<PathBuf> for Cow<'_, Path> {
610    fn into_maybe_undefined(self) -> MaybeUndefined<PathBuf> {
611        MaybeUndefined::Value(self.into())
612    }
613}
614
615impl IntoMaybeUndefined<serde_json::Value> for &str {
616    fn into_maybe_undefined(self) -> MaybeUndefined<serde_json::Value> {
617        MaybeUndefined::Value(self.into())
618    }
619}
620
621impl IntoMaybeUndefined<serde_json::Value> for String {
622    fn into_maybe_undefined(self) -> MaybeUndefined<serde_json::Value> {
623        MaybeUndefined::Value(self.into())
624    }
625}
626
627impl IntoMaybeUndefined<serde_json::Value> for Cow<'_, str> {
628    fn into_maybe_undefined(self) -> MaybeUndefined<serde_json::Value> {
629        MaybeUndefined::Value(self.into())
630    }
631}
632
633// ---- RequiredNullable<T> ----
634
635/// A value that must be present on the wire but whose value may be `null`.
636///
637/// Unlike `Option<T>`, which serde treats as an implicitly optional field
638/// (defaulting to `None` when absent), `RequiredNullable<T>` requires the key to be
639/// present during deserialization. A missing field will produce a
640/// deserialization error rather than silently defaulting to `None`.
641///
642/// On the wire this serializes identically to `Option<T>` — either `null` or
643/// the JSON representation of `T`.
644///
645/// **Note:** The `Deserialize` impl uses `serde_json::Value` internally to
646/// enforce the "required" constraint, so this type is JSON-only.
647///
648/// # Example
649///
650/// ```rust
651/// use agent_client_protocol_schema::RequiredNullable;
652/// use serde::{Serialize, Deserialize};
653///
654/// #[derive(Serialize, Deserialize, Debug, PartialEq)]
655/// struct Config {
656///     // MUST be present in JSON, but its value can be null
657///     value: RequiredNullable<String>,
658/// }
659///
660/// // ✅ Present with a value
661/// let c: Config = serde_json::from_str(r#"{"value":"hello"}"#).unwrap();
662/// assert_eq!(c.value, RequiredNullable::new("hello".to_string()));
663///
664/// // ✅ Present as null
665/// let c: Config = serde_json::from_str(r#"{"value":null}"#).unwrap();
666/// assert_eq!(c.value, RequiredNullable::null());
667///
668/// // ❌ Missing key — deserialization error
669/// assert!(serde_json::from_str::<Config>(r#"{}"#).is_err());
670/// ```
671#[cfg(feature = "unstable_llm_providers")]
672#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, JsonSchema)]
673#[schemars(with = "Option<T>", inline)]
674#[non_exhaustive]
675pub struct RequiredNullable<T>(pub Option<T>);
676
677#[cfg(feature = "unstable_llm_providers")]
678impl<T> Default for RequiredNullable<T> {
679    fn default() -> Self {
680        Self(None)
681    }
682}
683
684#[cfg(feature = "unstable_llm_providers")]
685impl<T: Serialize> Serialize for RequiredNullable<T> {
686    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
687        self.0.serialize(serializer)
688    }
689}
690
691#[cfg(feature = "unstable_llm_providers")]
692impl<'de, T: Deserialize<'de>> Deserialize<'de> for RequiredNullable<T> {
693    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
694        // Deserialize via serde_json::Value so that `deserialize_any` is called.
695        // serde's MissingFieldDeserializer errors on `deserialize_any` (good — the
696        // field is required), whereas `deserialize_option` silently returns None.
697        let value = serde_json::Value::deserialize(deserializer)?;
698        if value.is_null() {
699            Ok(RequiredNullable(None))
700        } else {
701            T::deserialize(value)
702                .map(RequiredNullable::new)
703                .map_err(serde::de::Error::custom)
704        }
705    }
706}
707
708#[cfg(feature = "unstable_llm_providers")]
709impl<T> RequiredNullable<T> {
710    /// Creates a `RequiredNullable` containing a value.
711    #[must_use]
712    pub fn new(value: T) -> Self {
713        Self(Some(value))
714    }
715
716    /// Creates a `RequiredNullable` representing `null`.
717    #[must_use]
718    pub fn null() -> Self {
719        Self(None)
720    }
721
722    /// Returns `true` if the value is `null`.
723    #[must_use]
724    pub fn is_null(&self) -> bool {
725        self.0.is_none()
726    }
727
728    /// Returns `true` if the value is present (not null).
729    #[must_use]
730    pub fn is_value(&self) -> bool {
731        self.0.is_some()
732    }
733
734    /// Returns a reference to the contained value, if present.
735    #[must_use]
736    pub fn value(&self) -> Option<&T> {
737        self.0.as_ref()
738    }
739
740    /// Returns a mutable reference to the contained value, if present.
741    #[must_use]
742    pub fn value_mut(&mut self) -> Option<&mut T> {
743        self.0.as_mut()
744    }
745
746    /// Converts into the inner `Option<T>`.
747    #[must_use]
748    pub fn into_inner(self) -> Option<T> {
749        self.0
750    }
751}
752
753#[cfg(feature = "unstable_llm_providers")]
754impl<T> From<Option<T>> for RequiredNullable<T> {
755    fn from(value: Option<T>) -> Self {
756        Self(value)
757    }
758}
759
760#[cfg(feature = "unstable_llm_providers")]
761impl<T> From<RequiredNullable<T>> for Option<T> {
762    fn from(value: RequiredNullable<T>) -> Self {
763        value.0
764    }
765}
766
767#[cfg(test)]
768mod tests {
769    use serde::{Deserialize, Serialize};
770    use serde_json::{from_value, json, to_value};
771
772    use super::*;
773
774    #[test]
775    fn test_maybe_undefined_serde() {
776        #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
777        struct A {
778            #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
779            a: MaybeUndefined<i32>,
780        }
781
782        assert_eq!(to_value(MaybeUndefined::Value(100i32)).unwrap(), json!(100));
783
784        assert_eq!(
785            from_value::<MaybeUndefined<i32>>(json!(100)).unwrap(),
786            MaybeUndefined::Value(100)
787        );
788        assert_eq!(
789            from_value::<MaybeUndefined<i32>>(json!(null)).unwrap(),
790            MaybeUndefined::Null
791        );
792
793        assert_eq!(
794            to_value(&A {
795                a: MaybeUndefined::Value(100i32)
796            })
797            .unwrap(),
798            json!({"a": 100})
799        );
800
801        assert_eq!(
802            to_value(&A {
803                a: MaybeUndefined::Null,
804            })
805            .unwrap(),
806            json!({ "a": null })
807        );
808
809        assert_eq!(
810            to_value(&A {
811                a: MaybeUndefined::Undefined,
812            })
813            .unwrap(),
814            json!({})
815        );
816
817        assert_eq!(
818            from_value::<A>(json!({"a": 100})).unwrap(),
819            A {
820                a: MaybeUndefined::Value(100i32)
821            }
822        );
823
824        assert_eq!(
825            from_value::<A>(json!({ "a": null })).unwrap(),
826            A {
827                a: MaybeUndefined::Null
828            }
829        );
830
831        assert_eq!(
832            from_value::<A>(json!({})).unwrap(),
833            A {
834                a: MaybeUndefined::Undefined
835            }
836        );
837    }
838
839    #[test]
840    fn test_maybe_undefined_to_nested_option() {
841        assert_eq!(Option::<Option<i32>>::from(MaybeUndefined::Undefined), None);
842
843        assert_eq!(
844            Option::<Option<i32>>::from(MaybeUndefined::Null),
845            Some(None)
846        );
847
848        assert_eq!(
849            Option::<Option<i32>>::from(MaybeUndefined::Value(42)),
850            Some(Some(42))
851        );
852    }
853
854    #[test]
855    fn test_as_opt_ref() {
856        let value = MaybeUndefined::<String>::Undefined;
857        let r = value.as_opt_ref();
858        assert_eq!(r, None);
859
860        let value = MaybeUndefined::<String>::Null;
861        let r = value.as_opt_ref();
862        assert_eq!(r, Some(None));
863
864        let value = MaybeUndefined::<String>::Value("abc".to_string());
865        let r = value.as_opt_ref();
866        assert_eq!(r, Some(Some(&"abc".to_string())));
867    }
868
869    #[test]
870    fn test_as_opt_deref() {
871        let value = MaybeUndefined::<String>::Undefined;
872        let r = value.as_opt_deref();
873        assert_eq!(r, None);
874
875        let value = MaybeUndefined::<String>::Null;
876        let r = value.as_opt_deref();
877        assert_eq!(r, Some(None));
878
879        let value = MaybeUndefined::<String>::Value("abc".to_string());
880        let r = value.as_opt_deref();
881        assert_eq!(r, Some(Some("abc")));
882    }
883
884    #[test]
885    fn test_contains_value() {
886        let test = "abc";
887
888        let mut value: MaybeUndefined<String> = MaybeUndefined::Undefined;
889        assert!(!value.contains_value(&test));
890
891        value = MaybeUndefined::Null;
892        assert!(!value.contains_value(&test));
893
894        value = MaybeUndefined::Value("abc".to_string());
895        assert!(value.contains_value(&test));
896    }
897
898    #[test]
899    fn test_contains() {
900        let test = Some("abc");
901        let none: Option<&str> = None;
902
903        let mut value: MaybeUndefined<String> = MaybeUndefined::Undefined;
904        assert!(!value.contains(test.as_ref()));
905        assert!(!value.contains(none.as_ref()));
906
907        value = MaybeUndefined::Null;
908        assert!(!value.contains(test.as_ref()));
909        assert!(value.contains(none.as_ref()));
910
911        value = MaybeUndefined::Value("abc".to_string());
912        assert!(value.contains(test.as_ref()));
913        assert!(!value.contains(none.as_ref()));
914    }
915
916    #[test]
917    fn test_map_value() {
918        let mut value: MaybeUndefined<i32> = MaybeUndefined::Undefined;
919        assert_eq!(value.map_value(|v| v > 2), MaybeUndefined::Undefined);
920
921        value = MaybeUndefined::Null;
922        assert_eq!(value.map_value(|v| v > 2), MaybeUndefined::Null);
923
924        value = MaybeUndefined::Value(5);
925        assert_eq!(value.map_value(|v| v > 2), MaybeUndefined::Value(true));
926    }
927
928    #[test]
929    fn test_map() {
930        let mut value: MaybeUndefined<i32> = MaybeUndefined::Undefined;
931        assert_eq!(value.map(|v| Some(v.is_some())), MaybeUndefined::Undefined);
932
933        value = MaybeUndefined::Null;
934        assert_eq!(
935            value.map(|v| Some(v.is_some())),
936            MaybeUndefined::Value(false)
937        );
938
939        value = MaybeUndefined::Value(5);
940        assert_eq!(
941            value.map(|v| Some(v.is_some())),
942            MaybeUndefined::Value(true)
943        );
944    }
945
946    #[test]
947    fn test_transpose() {
948        let mut value: MaybeUndefined<Result<i32, &'static str>> = MaybeUndefined::Undefined;
949        assert_eq!(value.transpose(), Ok(MaybeUndefined::Undefined));
950
951        value = MaybeUndefined::Null;
952        assert_eq!(value.transpose(), Ok(MaybeUndefined::Null));
953
954        value = MaybeUndefined::Value(Ok(5));
955        assert_eq!(value.transpose(), Ok(MaybeUndefined::Value(5)));
956
957        value = MaybeUndefined::Value(Err("error"));
958        assert_eq!(value.transpose(), Err("error"));
959    }
960
961    // ---- RequiredNullable tests ----
962
963    #[cfg(feature = "unstable_llm_providers")]
964    mod nullable_tests {
965        use super::*;
966        use serde_json::from_str;
967
968        #[derive(Serialize, Deserialize, Debug, PartialEq)]
969        struct Example {
970            value: RequiredNullable<String>,
971        }
972
973        #[test]
974        fn present_with_value() {
975            let example: Example = from_str(r#"{"value":"hello"}"#).unwrap();
976            assert_eq!(example.value, RequiredNullable(Some("hello".to_string())));
977        }
978
979        #[test]
980        fn present_as_null() {
981            let example: Example = from_str(r#"{"value":null}"#).unwrap();
982            assert_eq!(example.value, RequiredNullable(None));
983        }
984
985        #[test]
986        fn missing_key_fails() {
987            assert!(from_str::<Example>(r"{}").is_err());
988        }
989
990        #[test]
991        fn serialize_value() {
992            let example = Example {
993                value: RequiredNullable(Some("hello".to_string())),
994            };
995            assert_eq!(to_value(&example).unwrap(), json!({"value": "hello"}));
996        }
997
998        #[test]
999        fn serialize_null() {
1000            let example = Example {
1001                value: RequiredNullable(None),
1002            };
1003            assert_eq!(to_value(&example).unwrap(), json!({"value": null}));
1004        }
1005
1006        #[test]
1007        fn from_option() {
1008            let nullable: RequiredNullable<i32> = Some(42).into();
1009            assert_eq!(nullable, RequiredNullable(Some(42)));
1010
1011            let nullable: RequiredNullable<i32> = None.into();
1012            assert_eq!(nullable, RequiredNullable(None));
1013        }
1014
1015        #[test]
1016        fn into_option() {
1017            let option: Option<i32> = RequiredNullable(Some(42)).into();
1018            assert_eq!(option, Some(42));
1019
1020            let option: Option<i32> = RequiredNullable(None).into();
1021            assert_eq!(option, None);
1022        }
1023
1024        #[test]
1025        fn methods() {
1026            let value = RequiredNullable::new(42);
1027            assert!(value.is_value());
1028            assert!(!value.is_null());
1029            assert_eq!(value.value(), Some(&42));
1030            assert_eq!(value.into_inner(), Some(42));
1031
1032            let null: RequiredNullable<i32> = RequiredNullable::null();
1033            assert!(!null.is_value());
1034            assert!(null.is_null());
1035            assert_eq!(null.value(), None);
1036            assert_eq!(null.into_inner(), None);
1037        }
1038
1039        #[test]
1040        fn default_is_null() {
1041            let nullable: RequiredNullable<i32> = RequiredNullable::default();
1042            assert_eq!(nullable, RequiredNullable(None));
1043        }
1044    }
1045}