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