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