Skip to main content

agent_client_protocol_schema/
serde_util.rs

1//! Custom payload adapters, option-like field wrappers, and builder helpers for serde.
2//!
3//! ## Payload adapters
4//!
5//! - [`default_on_null`] — opt a defaultable payload into accepting null.
6//!
7//! ## Types
8//!
9//! - [`MaybeUndefined<T>`] — three-state: undefined (key absent), null, or value.
10//!
11//! ## Builder traits
12//!
13//! - [`IntoOption<T>`] — ergonomic conversion into `Option<T>` for builder methods.
14//! - [`IntoMaybeUndefined<T>`] — ergonomic conversion into `MaybeUndefined<T>` for builder methods.
15//!
16//! `MaybeUndefined` based on: <https://docs.rs/async-graphql/latest/src/async_graphql/types/maybe_undefined.rs.html>
17use std::{
18    borrow::Cow,
19    ffi::OsStr,
20    ops::Deref,
21    path::{Path, PathBuf},
22    sync::Arc,
23};
24
25use serde::{Deserialize, Deserializer, Serialize, Serializer};
26use serde_with::{DeserializeAs, de::DeserializeAsWrap};
27
28// ---- Default-on-null payloads ----
29
30/// Declares a defaultable payload whose `Deserialize` accepts null.
31///
32/// Declare the normal derives except `Deserialize` inside this macro. A private
33/// wire type derives deserialization from the same fields and attributes, so
34/// there is no second field definition to maintain and no public helper methods.
35/// `DefaultOnNull` wraps only deserialization of the entire payload; serialization
36/// and JSON Schema are still derived directly on the public type.
37///
38/// Opt in explicitly; implementing `Default` alone does not change wire behavior.
39macro_rules! default_on_null {
40    (
41        $(#[$attribute:meta])*
42        $visibility:vis struct $payload:ident {
43            $(
44                $(#[$field_attribute:meta])*
45                $field_visibility:vis $field:ident: $field_type:ty
46            ),* $(,)?
47        }
48    ) => {
49        $(#[$attribute])*
50        $visibility struct $payload {
51            $(
52                $(#[$field_attribute])*
53                $field_visibility $field: $field_type,
54            )*
55        }
56
57        impl<'de> serde::Deserialize<'de> for $payload {
58            fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
59                $(#[$attribute])*
60                #[derive(serde::Deserialize)]
61                struct Wire {
62                    $(
63                        $(#[$field_attribute])*
64                        $field_visibility $field: $field_type,
65                    )*
66                }
67
68                struct NonNull;
69
70                impl<'de> serde_with::DeserializeAs<'de, $payload> for NonNull {
71                    fn deserialize_as<D: serde::Deserializer<'de>>(
72                        deserializer: D,
73                    ) -> Result<$payload, D::Error> {
74                        let wire = <Wire as serde::Deserialize>::deserialize(deserializer)?;
75                        Ok($payload {
76                            $($field: wire.$field,)*
77                        })
78                    }
79                }
80
81                <serde_with::DefaultOnNull<NonNull> as serde_with::DeserializeAs<
82                    'de,
83                    Self,
84                >>::deserialize_as(deserializer)
85            }
86        }
87    };
88}
89
90pub(crate) use default_on_null;
91
92#[cfg(test)]
93mod default_on_null_tests {
94    use serde::{Deserialize, Serialize, de::DeserializeOwned};
95    use serde_json::{Value, json};
96
97    use crate::{
98        MaybeUndefined,
99        rpc::{JsonRpcMessage, Request, Response},
100        v1::{
101            self, LoadSessionResponse, NewSessionResponse, PromptResponse, ReadTextFileResponse,
102            RequestPermissionResponse, WaitForTerminalExitResponse, WriteTextFileResponse,
103        },
104    };
105
106    // Keep one inventory for the value, streaming, serialization, and schema checks.
107    // Feature gates match the payloads, so the inventory also runs without default
108    // features and with individual unstable features enabled.
109    macro_rules! for_each_defaultable_payload {
110        ($check:ident) => {
111            $check::<v1::AuthenticateResponse>();
112            $check::<v1::LogoutRequest>();
113            $check::<v1::LogoutResponse>();
114            $check::<v1::LoadSessionResponse>();
115            $check::<v1::ResumeSessionResponse>();
116            $check::<v1::CloseSessionResponse>();
117            $check::<v1::ListSessionsRequest>();
118            $check::<v1::DeleteSessionResponse>();
119            $check::<v1::SetSessionModeResponse>();
120            $check::<v1::WriteTextFileResponse>();
121            $check::<v1::ReleaseTerminalResponse>();
122            $check::<v1::KillTerminalResponse>();
123            $check::<v1::WaitForTerminalExitResponse>();
124
125            #[cfg(feature = "unstable_llm_providers")]
126            {
127                $check::<v1::ListProvidersRequest>();
128                $check::<v1::SetProviderResponse>();
129                $check::<v1::DisableProviderResponse>();
130            }
131
132            #[cfg(feature = "unstable_protocol_v2")]
133            {
134                use crate::v2;
135
136                $check::<v2::LoginAuthResponse>();
137                $check::<v2::LogoutAuthRequest>();
138                $check::<v2::LogoutAuthResponse>();
139                $check::<v2::ResumeSessionResponse>();
140                $check::<v2::CloseSessionResponse>();
141                $check::<v2::ListSessionsRequest>();
142                $check::<v2::DeleteSessionResponse>();
143
144                #[cfg(feature = "unstable_llm_providers")]
145                {
146                    $check::<v2::ListProvidersRequest>();
147                    $check::<v2::SetProviderResponse>();
148                    $check::<v2::DisableProviderResponse>();
149                }
150            }
151        };
152    }
153
154    fn assert_defaultable_payload<T>()
155    where
156        T: Default + DeserializeOwned + Serialize + PartialEq + std::fmt::Debug,
157    {
158        let name = std::any::type_name::<T>();
159        for value in [Value::Null, json!({})] {
160            assert_eq!(
161                serde_json::from_value::<T>(value).unwrap(),
162                T::default(),
163                "{name}",
164            );
165        }
166        assert_eq!(
167            serde_json::from_str::<T>("null").unwrap(),
168            T::default(),
169            "{name}",
170        );
171        assert_eq!(
172            serde_json::to_value(T::default()).unwrap(),
173            json!({}),
174            "{name}"
175        );
176
177        let metadata = json!({"_meta": {"example.com/key": ["preserve", 1, null]}});
178        let payload: T = serde_json::from_value(metadata.clone()).unwrap();
179        assert_eq!(serde_json::to_value(payload).unwrap(), metadata, "{name}");
180
181        for value in [json!(false), json!(42), json!("invalid")] {
182            assert!(serde_json::from_value::<T>(value).is_err(), "{name}");
183        }
184
185        // Opting in the payload must not swallow null in surrounding wrappers.
186        assert_eq!(
187            serde_json::from_value::<Option<T>>(Value::Null).unwrap(),
188            None,
189            "{name}",
190        );
191        assert_eq!(
192            serde_json::from_value::<MaybeUndefined<T>>(Value::Null).unwrap(),
193            MaybeUndefined::Null,
194            "{name}",
195        );
196    }
197
198    #[test]
199    fn defaultable_payloads_accept_null_without_losing_information() {
200        for_each_defaultable_payload!(assert_defaultable_payload);
201    }
202
203    #[test]
204    fn inherent_methods_do_not_bypass_null_handling() {
205        // These must resolve to the trait too, not a strict inherent helper.
206        assert_eq!(
207            WriteTextFileResponse::deserialize(Value::Null).unwrap(),
208            WriteTextFileResponse::default(),
209        );
210        assert_eq!(
211            LoadSessionResponse::deserialize(Value::Null).unwrap(),
212            LoadSessionResponse::default(),
213        );
214    }
215
216    #[test]
217    fn optional_payload_fields_are_preserved() {
218        let load = json!({
219            "modes": {
220                "currentModeId": "ask",
221                "availableModes": [{"id": "ask", "name": "Ask"}]
222            },
223            "configOptions": [],
224            "_meta": {"example.com/key": "value"}
225        });
226        let response: LoadSessionResponse = serde_json::from_value(load.clone()).unwrap();
227        assert_eq!(serde_json::to_value(response).unwrap(), load);
228
229        let list = json!({"cwd": "/workspace", "cursor": "next-page"});
230        let request: v1::ListSessionsRequest = serde_json::from_value(list.clone()).unwrap();
231        assert_eq!(serde_json::to_value(request).unwrap(), list);
232
233        #[cfg(feature = "unstable_protocol_v2")]
234        {
235            let request: crate::v2::ListSessionsRequest =
236                serde_json::from_value(list.clone()).unwrap();
237            assert_eq!(serde_json::to_value(request).unwrap(), list);
238        }
239    }
240
241    #[test]
242    fn default_terminal_exit_status_is_unknown_not_success() {
243        let response: WaitForTerminalExitResponse = serde_json::from_value(Value::Null).unwrap();
244        assert_eq!(response.exit_status.exit_code, None);
245        assert_eq!(response.exit_status.signal, None);
246        assert_eq!(response, WaitForTerminalExitResponse::default());
247
248        for value in [
249            json!({"exitCode": 0}),
250            json!({"exitCode": 17}),
251            json!({"signal": "SIGTERM"}),
252        ] {
253            let response: WaitForTerminalExitResponse =
254                serde_json::from_value(value.clone()).unwrap();
255            assert_eq!(serde_json::to_value(response).unwrap(), value);
256        }
257    }
258
259    #[test]
260    fn non_null_payloads_keep_the_derived_deserialization_behavior() {
261        // A default exists, but the field is still required in non-null input.
262        // DefaultOnNull must not become DefaultOnError.
263        super::default_on_null! {
264            #[derive(Default, Debug, Serialize, PartialEq)]
265            struct RequiredField {
266                count: u32,
267            }
268        }
269        #[derive(Deserialize)]
270        struct BaselineWrite {
271            #[serde(
272                default,
273                rename = "_meta",
274                with = "serde_with::As::<serde_with::DefaultOnError>"
275            )]
276            meta: Option<serde_json::Map<String, Value>>,
277        }
278
279        assert_eq!(
280            serde_json::from_value::<RequiredField>(Value::Null).unwrap(),
281            RequiredField::default(),
282        );
283        assert!(serde_json::from_value::<RequiredField>(json!({})).is_err());
284        assert!(serde_json::from_value::<RequiredField>(json!({"count": "invalid"})).is_err());
285
286        for value in [
287            json!({}),
288            json!({"_meta": {"example.com/key": true}}),
289            json!({"_meta": 42}),
290            json!({"modes": "invalid", "configOptions": "invalid"}),
291            json!([]),
292            json!([null]),
293            json!(false),
294            json!(42),
295            json!("invalid"),
296        ] {
297            assert_eq!(
298                serde_json::from_value::<WriteTextFileResponse>(value.clone())
299                    .map(|response| response.meta)
300                    .map_err(|_| ()),
301                serde_json::from_value::<BaselineWrite>(value)
302                    .map(|response| response.meta)
303                    .map_err(|_| ()),
304            );
305        }
306    }
307
308    #[test]
309    fn payloads_with_required_fields_still_reject_null() {
310        assert!(serde_json::from_value::<ReadTextFileResponse>(Value::Null).is_err());
311        assert!(serde_json::from_value::<RequestPermissionResponse>(Value::Null).is_err());
312        assert!(serde_json::from_value::<NewSessionResponse>(Value::Null).is_err());
313        assert!(serde_json::from_value::<PromptResponse>(Value::Null).is_err());
314        assert!(serde_json::from_value::<v1::InitializeResponse>(Value::Null).is_err());
315        assert!(serde_json::from_value::<v1::CreateTerminalResponse>(Value::Null).is_err());
316        assert!(serde_json::from_value::<v1::TerminalOutputResponse>(Value::Null).is_err());
317        assert!(serde_json::from_value::<v1::CreateElicitationResponse>(Value::Null).is_err());
318
319        #[cfg(feature = "unstable_protocol_v2")]
320        {
321            use crate::v2;
322
323            assert!(serde_json::from_value::<v2::InitializeResponse>(Value::Null).is_err());
324            assert!(serde_json::from_value::<v2::NewSessionResponse>(Value::Null).is_err());
325            assert!(serde_json::from_value::<v2::PromptResponse>(Value::Null).is_err());
326            assert!(serde_json::from_value::<v2::RequestPermissionResponse>(Value::Null).is_err());
327            assert!(serde_json::from_value::<v2::CreateElicitationResponse>(Value::Null).is_err());
328        }
329    }
330
331    #[test]
332    fn raw_response_nulls_are_not_rewritten() {
333        let extension: v1::ExtResponse = serde_json::from_value(Value::Null).unwrap();
334        assert_eq!(serde_json::to_value(extension).unwrap(), Value::Null);
335
336        #[cfg(feature = "unstable_mcp_over_acp")]
337        {
338            let mcp: v1::MessageMcpResponse =
339                serde_json::from_value(json!({"result": null})).unwrap();
340            assert_eq!(serde_json::to_value(mcp).unwrap(), json!({"result": null}));
341        }
342
343        #[cfg(feature = "unstable_protocol_v2")]
344        {
345            let extension: crate::v2::ExtResponse = serde_json::from_value(Value::Null).unwrap();
346            assert_eq!(serde_json::to_value(extension).unwrap(), Value::Null);
347
348            #[cfg(feature = "unstable_mcp_over_acp")]
349            {
350                let mcp: crate::v2::MessageMcpResponse =
351                    serde_json::from_value(json!({"result": null})).unwrap();
352                assert_eq!(serde_json::to_value(mcp).unwrap(), json!({"result": null}));
353            }
354        }
355    }
356
357    #[test]
358    fn optional_request_parameters_keep_their_existing_meaning() {
359        type LogoutRequest = Request<v1::LogoutRequest>;
360        for value in [
361            json!({"id": 1, "method": "logout"}),
362            json!({"id": 1, "method": "logout", "params": null}),
363        ] {
364            let request: LogoutRequest = serde_json::from_value(value).unwrap();
365            assert_eq!(request.params, None);
366        }
367        let request: LogoutRequest =
368            serde_json::from_value(json!({"id": 1, "method": "logout", "params": {}})).unwrap();
369        assert_eq!(request.params, Some(v1::LogoutRequest::default()));
370    }
371
372    #[test]
373    fn nullable_fields_keep_their_existing_meaning() {
374        #[derive(Debug, Deserialize, PartialEq)]
375        struct Container {
376            optional: Option<WriteTextFileResponse>,
377            #[serde(default)]
378            patch: MaybeUndefined<WriteTextFileResponse>,
379        }
380
381        assert_eq!(
382            serde_json::from_value::<Option<WriteTextFileResponse>>(Value::Null).unwrap(),
383            None,
384        );
385        assert_eq!(
386            serde_json::from_value::<MaybeUndefined<WriteTextFileResponse>>(Value::Null).unwrap(),
387            MaybeUndefined::Null,
388        );
389        assert_eq!(
390            serde_json::from_value::<Container>(json!({})).unwrap(),
391            Container {
392                optional: None,
393                patch: MaybeUndefined::Undefined,
394            },
395        );
396        assert_eq!(
397            serde_json::from_value::<Container>(json!({"optional": null, "patch": null})).unwrap(),
398            Container {
399                optional: None,
400                patch: MaybeUndefined::Null,
401            },
402        );
403        assert_eq!(
404            serde_json::from_value::<Container>(json!({"optional": {}, "patch": {}})).unwrap(),
405            Container {
406                optional: Some(WriteTextFileResponse::default()),
407                patch: MaybeUndefined::Value(WriteTextFileResponse::default()),
408            },
409        );
410    }
411
412    #[test]
413    fn response_result_is_required_even_when_its_payload_accepts_null() {
414        type WriteResponse = Response<WriteTextFileResponse, Value>;
415        let response: WriteResponse =
416            serde_json::from_value(json!({"id": 1, "result": null})).unwrap();
417        assert_eq!(
418            response,
419            Response::new(1, Ok(WriteTextFileResponse::default())),
420        );
421        assert!(serde_json::from_value::<WriteResponse>(json!({"id": 1})).is_err());
422        assert!(
423            serde_json::from_value::<JsonRpcMessage<WriteResponse>>(
424                json!({"jsonrpc": "2.0", "id": 1})
425            )
426            .is_err()
427        );
428
429        let error = json!({"code": -32603, "message": "Internal error"});
430        let response: JsonRpcMessage<WriteResponse> = serde_json::from_value(json!({
431            "jsonrpc": "2.0",
432            "id": 1,
433            "error": error.clone()
434        }))
435        .unwrap();
436        assert_eq!(response.into_inner(), Response::new(1, Err(error)));
437    }
438
439    #[cfg(feature = "schemars")]
440    #[test]
441    fn defaultable_payload_schemas_still_require_objects() {
442        fn assert_object_schema<T: schemars::JsonSchema>() {
443            let schema = serde_json::to_value(schemars::schema_for!(T)).unwrap();
444            assert_eq!(schema["type"], "object");
445            assert!(schema.get("anyOf").is_none());
446        }
447        for_each_defaultable_payload!(assert_object_schema);
448    }
449}
450
451// ---- IntoOption ----
452
453/// Utility trait for builder methods for optional values.
454/// This allows the caller to either pass in the value itself without wrapping it in `Some`,
455/// or to just pass in an Option if that is what they have.
456pub trait IntoOption<T> {
457    /// Converts this value into an optional builder argument.
458    fn into_option(self) -> Option<T>;
459}
460
461impl<T> IntoOption<T> for Option<T> {
462    fn into_option(self) -> Option<T> {
463        self
464    }
465}
466
467impl<T> IntoOption<T> for T {
468    fn into_option(self) -> Option<T> {
469        Some(self)
470    }
471}
472
473impl IntoOption<String> for &str {
474    fn into_option(self) -> Option<String> {
475        Some(self.into())
476    }
477}
478
479impl IntoOption<String> for &mut str {
480    fn into_option(self) -> Option<String> {
481        Some(self.into())
482    }
483}
484
485impl IntoOption<String> for &String {
486    fn into_option(self) -> Option<String> {
487        Some(self.into())
488    }
489}
490
491impl IntoOption<String> for Box<str> {
492    fn into_option(self) -> Option<String> {
493        Some(self.into())
494    }
495}
496
497impl IntoOption<String> for Cow<'_, str> {
498    fn into_option(self) -> Option<String> {
499        Some(self.into())
500    }
501}
502
503impl IntoOption<String> for Arc<str> {
504    fn into_option(self) -> Option<String> {
505        Some(self.to_string())
506    }
507}
508
509impl<T: ?Sized + AsRef<OsStr>> IntoOption<PathBuf> for &T {
510    fn into_option(self) -> Option<PathBuf> {
511        Some(self.into())
512    }
513}
514
515impl IntoOption<PathBuf> for Box<Path> {
516    fn into_option(self) -> Option<PathBuf> {
517        Some(self.into())
518    }
519}
520
521impl IntoOption<PathBuf> for Cow<'_, Path> {
522    fn into_option(self) -> Option<PathBuf> {
523        Some(self.into())
524    }
525}
526
527impl IntoOption<serde_json::Value> for &str {
528    fn into_option(self) -> Option<serde_json::Value> {
529        Some(self.into())
530    }
531}
532
533impl IntoOption<serde_json::Value> for String {
534    fn into_option(self) -> Option<serde_json::Value> {
535        Some(self.into())
536    }
537}
538
539impl IntoOption<serde_json::Value> for Cow<'_, str> {
540    fn into_option(self) -> Option<serde_json::Value> {
541        Some(self.into())
542    }
543}
544
545// ---- MaybeUndefined ----
546
547/// Similar to `Option`, but it has three states, `undefined`, `null` and `x`.
548///
549/// When using with Serde, you will likely want to skip serialization of `undefined`
550/// and add a `default` for deserialization.
551///
552/// # Example
553///
554/// ```rust
555/// use agent_client_protocol_schema::MaybeUndefined;
556/// use serde::{Serialize, Deserialize};
557///
558/// #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
559/// struct A {
560///     #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
561///     a: MaybeUndefined<i32>,
562/// }
563/// ```
564#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
565#[derive(Copy, Clone, Default, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
566#[cfg_attr(feature = "schemars", schemars(with = "Option<Option<T>>", inline))]
567#[expect(clippy::exhaustive_enums)]
568pub enum MaybeUndefined<T> {
569    /// The field was not present.
570    #[default]
571    Undefined,
572    /// The field was present with a JSON `null` value.
573    Null,
574    /// The field was present with a non-null value.
575    Value(T),
576}
577
578impl<T> MaybeUndefined<T> {
579    /// Returns true if the `MaybeUndefined<T>` is undefined.
580    #[inline]
581    pub const fn is_undefined(&self) -> bool {
582        matches!(self, MaybeUndefined::Undefined)
583    }
584
585    /// Returns true if the `MaybeUndefined<T>` is null.
586    #[inline]
587    pub const fn is_null(&self) -> bool {
588        matches!(self, MaybeUndefined::Null)
589    }
590
591    /// Returns true if the `MaybeUndefined<T>` contains value.
592    #[inline]
593    pub const fn is_value(&self) -> bool {
594        matches!(self, MaybeUndefined::Value(_))
595    }
596
597    /// Borrow the value, returns `None` if the `MaybeUndefined<T>` is
598    /// `undefined` or `null`, otherwise returns `Some(T)`.
599    #[inline]
600    pub const fn value(&self) -> Option<&T> {
601        match self {
602            MaybeUndefined::Value(value) => Some(value),
603            _ => None,
604        }
605    }
606
607    /// Converts the `MaybeUndefined<T>` to `Option<T>`.
608    #[inline]
609    pub fn take(self) -> Option<T> {
610        match self {
611            MaybeUndefined::Value(value) => Some(value),
612            _ => None,
613        }
614    }
615
616    /// Converts the `MaybeUndefined<T>` to `Option<Option<T>>`.
617    #[inline]
618    pub const fn as_opt_ref(&self) -> Option<Option<&T>> {
619        match self {
620            MaybeUndefined::Undefined => None,
621            MaybeUndefined::Null => Some(None),
622            MaybeUndefined::Value(value) => Some(Some(value)),
623        }
624    }
625
626    /// Converts the `MaybeUndefined<T>` to `Option<Option<&U>>`.
627    #[inline]
628    pub fn as_opt_deref<U>(&self) -> Option<Option<&U>>
629    where
630        U: ?Sized,
631        T: Deref<Target = U>,
632    {
633        match self {
634            MaybeUndefined::Undefined => None,
635            MaybeUndefined::Null => Some(None),
636            MaybeUndefined::Value(value) => Some(Some(&**value)),
637        }
638    }
639
640    /// Returns `true` if the `MaybeUndefined<T>` contains the given value.
641    #[inline]
642    pub fn contains_value<U>(&self, x: &U) -> bool
643    where
644        U: PartialEq<T>,
645    {
646        match self {
647            MaybeUndefined::Value(y) => x == y,
648            _ => false,
649        }
650    }
651
652    /// Returns `true` if the `MaybeUndefined<T>` contains the given nullable
653    /// value.
654    #[inline]
655    pub fn contains<U>(&self, x: Option<&U>) -> bool
656    where
657        U: PartialEq<T>,
658    {
659        match self {
660            MaybeUndefined::Value(y) => matches!(x, Some(v) if v == y),
661            MaybeUndefined::Null => x.is_none(),
662            MaybeUndefined::Undefined => false,
663        }
664    }
665
666    /// Maps a `MaybeUndefined<T>` to `MaybeUndefined<U>` by applying a function
667    /// to the contained nullable value
668    #[inline]
669    pub fn map<U, F: FnOnce(Option<T>) -> Option<U>>(self, f: F) -> MaybeUndefined<U> {
670        match self {
671            MaybeUndefined::Value(v) => match f(Some(v)) {
672                Some(v) => MaybeUndefined::Value(v),
673                None => MaybeUndefined::Null,
674            },
675            MaybeUndefined::Null => match f(None) {
676                Some(v) => MaybeUndefined::Value(v),
677                None => MaybeUndefined::Null,
678            },
679            MaybeUndefined::Undefined => MaybeUndefined::Undefined,
680        }
681    }
682
683    /// Maps a `MaybeUndefined<T>` to `MaybeUndefined<U>` by applying a function
684    /// to the contained value
685    #[inline]
686    pub fn map_value<U, F: FnOnce(T) -> U>(self, f: F) -> MaybeUndefined<U> {
687        match self {
688            MaybeUndefined::Value(v) => MaybeUndefined::Value(f(v)),
689            MaybeUndefined::Null => MaybeUndefined::Null,
690            MaybeUndefined::Undefined => MaybeUndefined::Undefined,
691        }
692    }
693
694    /// Update `value` if the `MaybeUndefined<T>` is not undefined.
695    ///
696    /// # Example
697    ///
698    /// ```rust
699    /// use agent_client_protocol_schema::MaybeUndefined;
700    ///
701    /// let mut value = None;
702    ///
703    /// MaybeUndefined::Value(10i32).update_to(&mut value);
704    /// assert_eq!(value, Some(10));
705    ///
706    /// MaybeUndefined::Undefined.update_to(&mut value);
707    /// assert_eq!(value, Some(10));
708    ///
709    /// MaybeUndefined::Null.update_to(&mut value);
710    /// assert_eq!(value, None);
711    /// ```
712    pub fn update_to(self, value: &mut Option<T>) {
713        match self {
714            MaybeUndefined::Value(new) => *value = Some(new),
715            MaybeUndefined::Null => *value = None,
716            MaybeUndefined::Undefined => {}
717        }
718    }
719}
720
721impl<T, E> MaybeUndefined<Result<T, E>> {
722    /// Transposes a `MaybeUndefined` of a [`Result`] into a [`Result`] of a
723    /// `MaybeUndefined`.
724    ///
725    /// [`MaybeUndefined::Undefined`] will be mapped to
726    /// [`Ok`]`(`[`MaybeUndefined::Undefined`]`)`. [`MaybeUndefined::Null`]
727    /// will be mapped to [`Ok`]`(`[`MaybeUndefined::Null`]`)`.
728    /// [`MaybeUndefined::Value`]`(`[`Ok`]`(_))` and
729    /// [`MaybeUndefined::Value`]`(`[`Err`]`(_))` will be mapped to
730    /// [`Ok`]`(`[`MaybeUndefined::Value`]`(_))` and [`Err`]`(_)`.
731    ///
732    /// # Errors
733    ///
734    /// Returns an error if the input is [`MaybeUndefined::Value`]`(`[`Err`]`(_))`.
735    #[inline]
736    pub fn transpose(self) -> Result<MaybeUndefined<T>, E> {
737        match self {
738            MaybeUndefined::Undefined => Ok(MaybeUndefined::Undefined),
739            MaybeUndefined::Null => Ok(MaybeUndefined::Null),
740            MaybeUndefined::Value(Ok(v)) => Ok(MaybeUndefined::Value(v)),
741            MaybeUndefined::Value(Err(e)) => Err(e),
742        }
743    }
744}
745
746impl<T: Serialize> Serialize for MaybeUndefined<T> {
747    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
748        match self {
749            MaybeUndefined::Value(value) => value.serialize(serializer),
750            MaybeUndefined::Null => serializer.serialize_none(),
751            MaybeUndefined::Undefined => serializer.serialize_unit(),
752        }
753    }
754}
755
756impl<'de, T> Deserialize<'de> for MaybeUndefined<T>
757where
758    T: Deserialize<'de>,
759{
760    fn deserialize<D>(deserializer: D) -> Result<MaybeUndefined<T>, D::Error>
761    where
762        D: Deserializer<'de>,
763    {
764        Option::<T>::deserialize(deserializer).map(|value| match value {
765            Some(value) => MaybeUndefined::Value(value),
766            None => MaybeUndefined::Null,
767        })
768    }
769}
770
771impl<T> From<MaybeUndefined<T>> for Option<Option<T>> {
772    fn from(maybe_undefined: MaybeUndefined<T>) -> Self {
773        match maybe_undefined {
774            MaybeUndefined::Undefined => None,
775            MaybeUndefined::Null => Some(None),
776            MaybeUndefined::Value(value) => Some(Some(value)),
777        }
778    }
779}
780
781impl<T> From<Option<Option<T>>> for MaybeUndefined<T> {
782    fn from(value: Option<Option<T>>) -> Self {
783        match value {
784            Some(Some(value)) => Self::Value(value),
785            Some(None) => Self::Null,
786            None => Self::Undefined,
787        }
788    }
789}
790
791impl<'de, T, TAs> DeserializeAs<'de, MaybeUndefined<T>> for MaybeUndefined<TAs>
792where
793    TAs: DeserializeAs<'de, T>,
794{
795    fn deserialize_as<D>(deserializer: D) -> Result<MaybeUndefined<T>, D::Error>
796    where
797        D: Deserializer<'de>,
798    {
799        Option::<DeserializeAsWrap<T, TAs>>::deserialize(deserializer).map(|value| match value {
800            Some(value) => MaybeUndefined::Value(value.into_inner()),
801            None => MaybeUndefined::Null,
802        })
803    }
804}
805
806/// Utility trait for builder methods for optional values.
807/// This allows the caller to either pass in the value itself without wrapping it in `Some`,
808/// or to just pass in an Option if that is what they have, or set it back to undefined.
809pub trait IntoMaybeUndefined<T> {
810    /// Converts this value into a three-state builder argument.
811    fn into_maybe_undefined(self) -> MaybeUndefined<T>;
812}
813
814impl<T> IntoMaybeUndefined<T> for T {
815    fn into_maybe_undefined(self) -> MaybeUndefined<T> {
816        MaybeUndefined::Value(self)
817    }
818}
819
820impl<T> IntoMaybeUndefined<T> for Option<T> {
821    fn into_maybe_undefined(self) -> MaybeUndefined<T> {
822        match self {
823            Some(value) => MaybeUndefined::Value(value),
824            None => MaybeUndefined::Null,
825        }
826    }
827}
828
829impl<T> IntoMaybeUndefined<T> for MaybeUndefined<T> {
830    fn into_maybe_undefined(self) -> MaybeUndefined<T> {
831        self
832    }
833}
834
835impl IntoMaybeUndefined<String> for &str {
836    fn into_maybe_undefined(self) -> MaybeUndefined<String> {
837        MaybeUndefined::Value(self.into())
838    }
839}
840
841impl IntoMaybeUndefined<String> for &mut str {
842    fn into_maybe_undefined(self) -> MaybeUndefined<String> {
843        MaybeUndefined::Value(self.into())
844    }
845}
846
847impl IntoMaybeUndefined<String> for &String {
848    fn into_maybe_undefined(self) -> MaybeUndefined<String> {
849        MaybeUndefined::Value(self.into())
850    }
851}
852
853impl IntoMaybeUndefined<String> for Box<str> {
854    fn into_maybe_undefined(self) -> MaybeUndefined<String> {
855        MaybeUndefined::Value(self.into())
856    }
857}
858
859impl IntoMaybeUndefined<String> for Cow<'_, str> {
860    fn into_maybe_undefined(self) -> MaybeUndefined<String> {
861        MaybeUndefined::Value(self.into())
862    }
863}
864
865impl IntoMaybeUndefined<String> for Arc<str> {
866    fn into_maybe_undefined(self) -> MaybeUndefined<String> {
867        MaybeUndefined::Value(self.to_string())
868    }
869}
870
871impl<T: ?Sized + AsRef<OsStr>> IntoMaybeUndefined<PathBuf> for &T {
872    fn into_maybe_undefined(self) -> MaybeUndefined<PathBuf> {
873        MaybeUndefined::Value(self.into())
874    }
875}
876
877impl IntoMaybeUndefined<PathBuf> for Box<Path> {
878    fn into_maybe_undefined(self) -> MaybeUndefined<PathBuf> {
879        MaybeUndefined::Value(self.into())
880    }
881}
882
883impl IntoMaybeUndefined<PathBuf> for Cow<'_, Path> {
884    fn into_maybe_undefined(self) -> MaybeUndefined<PathBuf> {
885        MaybeUndefined::Value(self.into())
886    }
887}
888
889impl IntoMaybeUndefined<serde_json::Value> for &str {
890    fn into_maybe_undefined(self) -> MaybeUndefined<serde_json::Value> {
891        MaybeUndefined::Value(self.into())
892    }
893}
894
895impl IntoMaybeUndefined<serde_json::Value> for String {
896    fn into_maybe_undefined(self) -> MaybeUndefined<serde_json::Value> {
897        MaybeUndefined::Value(self.into())
898    }
899}
900
901impl IntoMaybeUndefined<serde_json::Value> for Cow<'_, str> {
902    fn into_maybe_undefined(self) -> MaybeUndefined<serde_json::Value> {
903        MaybeUndefined::Value(self.into())
904    }
905}
906
907#[cfg(test)]
908mod tests {
909    use serde::{Deserialize, Serialize};
910    use serde_json::{from_value, json, to_value};
911
912    use super::*;
913
914    #[test]
915    fn test_maybe_undefined_serde() {
916        #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
917        struct A {
918            #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
919            a: MaybeUndefined<i32>,
920        }
921
922        assert_eq!(to_value(MaybeUndefined::Value(100i32)).unwrap(), json!(100));
923
924        assert_eq!(
925            from_value::<MaybeUndefined<i32>>(json!(100)).unwrap(),
926            MaybeUndefined::Value(100)
927        );
928        assert_eq!(
929            from_value::<MaybeUndefined<i32>>(json!(null)).unwrap(),
930            MaybeUndefined::Null
931        );
932
933        assert_eq!(
934            to_value(&A {
935                a: MaybeUndefined::Value(100i32)
936            })
937            .unwrap(),
938            json!({"a": 100})
939        );
940
941        assert_eq!(
942            to_value(&A {
943                a: MaybeUndefined::Null,
944            })
945            .unwrap(),
946            json!({ "a": null })
947        );
948
949        assert_eq!(
950            to_value(&A {
951                a: MaybeUndefined::Undefined,
952            })
953            .unwrap(),
954            json!({})
955        );
956
957        assert_eq!(
958            from_value::<A>(json!({"a": 100})).unwrap(),
959            A {
960                a: MaybeUndefined::Value(100i32)
961            }
962        );
963
964        assert_eq!(
965            from_value::<A>(json!({ "a": null })).unwrap(),
966            A {
967                a: MaybeUndefined::Null
968            }
969        );
970
971        assert_eq!(
972            from_value::<A>(json!({})).unwrap(),
973            A {
974                a: MaybeUndefined::Undefined
975            }
976        );
977    }
978
979    #[test]
980    fn test_maybe_undefined_to_nested_option() {
981        assert_eq!(Option::<Option<i32>>::from(MaybeUndefined::Undefined), None);
982
983        assert_eq!(
984            Option::<Option<i32>>::from(MaybeUndefined::Null),
985            Some(None)
986        );
987
988        assert_eq!(
989            Option::<Option<i32>>::from(MaybeUndefined::Value(42)),
990            Some(Some(42))
991        );
992    }
993
994    #[test]
995    fn test_as_opt_ref() {
996        let value = MaybeUndefined::<String>::Undefined;
997        let r = value.as_opt_ref();
998        assert_eq!(r, None);
999
1000        let value = MaybeUndefined::<String>::Null;
1001        let r = value.as_opt_ref();
1002        assert_eq!(r, Some(None));
1003
1004        let value = MaybeUndefined::<String>::Value("abc".to_string());
1005        let r = value.as_opt_ref();
1006        assert_eq!(r, Some(Some(&"abc".to_string())));
1007    }
1008
1009    #[test]
1010    fn test_as_opt_deref() {
1011        let value = MaybeUndefined::<String>::Undefined;
1012        let r = value.as_opt_deref();
1013        assert_eq!(r, None);
1014
1015        let value = MaybeUndefined::<String>::Null;
1016        let r = value.as_opt_deref();
1017        assert_eq!(r, Some(None));
1018
1019        let value = MaybeUndefined::<String>::Value("abc".to_string());
1020        let r = value.as_opt_deref();
1021        assert_eq!(r, Some(Some("abc")));
1022    }
1023
1024    #[test]
1025    fn test_contains_value() {
1026        let test = "abc";
1027
1028        let mut value: MaybeUndefined<String> = MaybeUndefined::Undefined;
1029        assert!(!value.contains_value(&test));
1030
1031        value = MaybeUndefined::Null;
1032        assert!(!value.contains_value(&test));
1033
1034        value = MaybeUndefined::Value("abc".to_string());
1035        assert!(value.contains_value(&test));
1036    }
1037
1038    #[test]
1039    fn test_contains() {
1040        let test = Some("abc");
1041        let none: Option<&str> = None;
1042
1043        let mut value: MaybeUndefined<String> = MaybeUndefined::Undefined;
1044        assert!(!value.contains(test.as_ref()));
1045        assert!(!value.contains(none.as_ref()));
1046
1047        value = MaybeUndefined::Null;
1048        assert!(!value.contains(test.as_ref()));
1049        assert!(value.contains(none.as_ref()));
1050
1051        value = MaybeUndefined::Value("abc".to_string());
1052        assert!(value.contains(test.as_ref()));
1053        assert!(!value.contains(none.as_ref()));
1054    }
1055
1056    #[test]
1057    fn test_map_value() {
1058        let mut value: MaybeUndefined<i32> = MaybeUndefined::Undefined;
1059        assert_eq!(value.map_value(|v| v > 2), MaybeUndefined::Undefined);
1060
1061        value = MaybeUndefined::Null;
1062        assert_eq!(value.map_value(|v| v > 2), MaybeUndefined::Null);
1063
1064        value = MaybeUndefined::Value(5);
1065        assert_eq!(value.map_value(|v| v > 2), MaybeUndefined::Value(true));
1066    }
1067
1068    #[test]
1069    fn test_map() {
1070        let mut value: MaybeUndefined<i32> = MaybeUndefined::Undefined;
1071        assert_eq!(value.map(|v| Some(v.is_some())), MaybeUndefined::Undefined);
1072
1073        value = MaybeUndefined::Null;
1074        assert_eq!(
1075            value.map(|v| Some(v.is_some())),
1076            MaybeUndefined::Value(false)
1077        );
1078
1079        value = MaybeUndefined::Value(5);
1080        assert_eq!(
1081            value.map(|v| Some(v.is_some())),
1082            MaybeUndefined::Value(true)
1083        );
1084    }
1085
1086    #[test]
1087    fn test_transpose() {
1088        let mut value: MaybeUndefined<Result<i32, &'static str>> = MaybeUndefined::Undefined;
1089        assert_eq!(value.transpose(), Ok(MaybeUndefined::Undefined));
1090
1091        value = MaybeUndefined::Null;
1092        assert_eq!(value.transpose(), Ok(MaybeUndefined::Null));
1093
1094        value = MaybeUndefined::Value(Ok(5));
1095        assert_eq!(value.transpose(), Ok(MaybeUndefined::Value(5)));
1096
1097        value = MaybeUndefined::Value(Err("error"));
1098        assert_eq!(value.transpose(), Err("error"));
1099    }
1100}