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