Skip to main content

antigravity_codes/
wire.rs

1//! serde adapters for the quirks of the protobuf-JSON encoding.
2//!
3//! The harness serialises its protobuf messages with the canonical JSON
4//! mapping, which departs from what `#[derive(Deserialize)]` would assume in
5//! three places:
6//!
7//! - **64-bit integers are strings.** `{"seqNum": "17"}`, not `{"seqNum": 17}`.
8//!   Decoders are expected to accept both, so [`opt_int`] and [`vec_int`] take
9//!   either and always emit the string form.
10//! - **`bytes` is base64.** [`opt_bytes`] and [`vec_bytes`] decode standard and
11//!   URL-safe alphabets, with or without padding, and emit standard padded.
12//! - **Enums are value names**, though numbers are also legal input. Generated
13//!   enums decode through [`EnumRepr`] and keep unrecognised values rather than
14//!   failing, so a harness newer than this crate stays readable.
15
16use std::fmt::Display;
17use std::str::FromStr;
18
19use base64::engine::general_purpose::{STANDARD, STANDARD_NO_PAD, URL_SAFE, URL_SAFE_NO_PAD};
20use base64::Engine as _;
21use serde::de::{self, Unexpected, Visitor};
22use serde::{Deserialize, Deserializer, Serializer};
23
24/// How a protobuf enum arrived on the wire: by name, or by number.
25#[derive(Debug, Clone, Deserialize)]
26#[serde(untagged)]
27pub enum EnumRepr {
28    /// The canonical form — the proto value name, e.g. `"STATE_DONE"`.
29    Name(String),
30    /// The numeric form, which the JSON mapping also permits.
31    Number(i32),
32}
33
34fn decode_base64<E: de::Error>(s: &str) -> Result<Vec<u8>, E> {
35    for engine in [&STANDARD, &STANDARD_NO_PAD, &URL_SAFE, &URL_SAFE_NO_PAD] {
36        if let Ok(bytes) = engine.decode(s) {
37            return Ok(bytes);
38        }
39    }
40    Err(E::invalid_value(
41        Unexpected::Str(s),
42        &"base64-encoded bytes",
43    ))
44}
45
46struct IntVisitor<T>(std::marker::PhantomData<T>);
47
48impl<T> Visitor<'_> for IntVisitor<T>
49where
50    T: FromStr + TryFrom<i64> + TryFrom<u64>,
51    <T as FromStr>::Err: Display,
52{
53    type Value = T;
54
55    fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        f.write_str("an integer, as a JSON number or a decimal string")
57    }
58
59    fn visit_str<E: de::Error>(self, v: &str) -> Result<T, E> {
60        v.parse().map_err(|e: <T as FromStr>::Err| E::custom(e))
61    }
62
63    fn visit_i64<E: de::Error>(self, v: i64) -> Result<T, E> {
64        T::try_from(v).map_err(|_| E::custom(format!("integer {v} out of range")))
65    }
66
67    fn visit_u64<E: de::Error>(self, v: u64) -> Result<T, E> {
68        T::try_from(v).map_err(|_| E::custom(format!("integer {v} out of range")))
69    }
70}
71
72fn deserialize_int<'de, T, D>(d: D) -> Result<T, D::Error>
73where
74    T: FromStr + TryFrom<i64> + TryFrom<u64>,
75    <T as FromStr>::Err: Display,
76    D: Deserializer<'de>,
77{
78    d.deserialize_any(IntVisitor(std::marker::PhantomData))
79}
80
81/// A 64-bit integer that may be absent, encoded as a JSON string.
82pub mod opt_int {
83    use super::*;
84
85    /// Emits the canonical string form, or `null` when absent.
86    pub fn serialize<T: Display, S: Serializer>(v: &Option<T>, s: S) -> Result<S::Ok, S::Error> {
87        match v {
88            Some(v) => s.serialize_str(&v.to_string()),
89            None => s.serialize_none(),
90        }
91    }
92
93    /// Accepts a string, a number, or `null`.
94    pub fn deserialize<'de, T, D>(d: D) -> Result<Option<T>, D::Error>
95    where
96        T: FromStr + TryFrom<i64> + TryFrom<u64>,
97        <T as FromStr>::Err: Display,
98        D: Deserializer<'de>,
99    {
100        struct OptVisitor<T>(std::marker::PhantomData<T>);
101
102        impl<'de, T> Visitor<'de> for OptVisitor<T>
103        where
104            T: FromStr + TryFrom<i64> + TryFrom<u64>,
105            <T as FromStr>::Err: Display,
106        {
107            type Value = Option<T>;
108
109            fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110                f.write_str("an optional integer")
111            }
112
113            fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
114                Ok(None)
115            }
116
117            fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
118                Ok(None)
119            }
120
121            fn visit_some<D: Deserializer<'de>>(self, d: D) -> Result<Self::Value, D::Error> {
122                super::deserialize_int(d).map(Some)
123            }
124        }
125
126        d.deserialize_option(OptVisitor(std::marker::PhantomData))
127    }
128}
129
130/// A repeated 64-bit integer field, encoded as JSON strings.
131pub mod vec_int {
132    use super::*;
133
134    /// Emits each element in the canonical string form.
135    pub fn serialize<T: Display, S: Serializer>(v: &[T], s: S) -> Result<S::Ok, S::Error> {
136        use serde::ser::SerializeSeq;
137        let mut seq = s.serialize_seq(Some(v.len()))?;
138        for item in v {
139            seq.serialize_element(&item.to_string())?;
140        }
141        seq.end()
142    }
143
144    /// Accepts a sequence whose elements are strings or numbers.
145    pub fn deserialize<'de, T, D>(d: D) -> Result<Vec<T>, D::Error>
146    where
147        T: FromStr + TryFrom<i64> + TryFrom<u64>,
148        <T as FromStr>::Err: Display,
149        D: Deserializer<'de>,
150    {
151        struct SeqVisitor<T>(std::marker::PhantomData<T>);
152
153        impl<'de, T> Visitor<'de> for SeqVisitor<T>
154        where
155            T: FromStr + TryFrom<i64> + TryFrom<u64>,
156            <T as FromStr>::Err: Display,
157        {
158            type Value = Vec<T>;
159
160            fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161                f.write_str("a sequence of integers")
162            }
163
164            fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
165                Ok(Vec::new())
166            }
167
168            fn visit_seq<A: de::SeqAccess<'de>>(self, mut a: A) -> Result<Self::Value, A::Error> {
169                let mut out = Vec::with_capacity(a.size_hint().unwrap_or(0));
170                while let Some(v) = a.next_element_seed(IntSeed(std::marker::PhantomData))? {
171                    out.push(v);
172                }
173                Ok(out)
174            }
175        }
176
177        struct IntSeed<T>(std::marker::PhantomData<T>);
178
179        impl<'de, T> de::DeserializeSeed<'de> for IntSeed<T>
180        where
181            T: FromStr + TryFrom<i64> + TryFrom<u64>,
182            <T as FromStr>::Err: Display,
183        {
184            type Value = T;
185
186            fn deserialize<D: Deserializer<'de>>(self, d: D) -> Result<T, D::Error> {
187                super::deserialize_int(d)
188            }
189        }
190
191        d.deserialize_any(SeqVisitor(std::marker::PhantomData))
192    }
193}
194
195/// A `bytes` field that may be absent, encoded as base64.
196pub mod opt_bytes {
197    use super::*;
198
199    /// Emits standard padded base64, or `null` when absent.
200    pub fn serialize<S: Serializer>(v: &Option<Vec<u8>>, s: S) -> Result<S::Ok, S::Error> {
201        match v {
202            Some(b) => s.serialize_str(&STANDARD.encode(b)),
203            None => s.serialize_none(),
204        }
205    }
206
207    /// Accepts standard or URL-safe base64, padded or not.
208    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Vec<u8>>, D::Error> {
209        let raw = Option::<String>::deserialize(d)?;
210        raw.map(|s| decode_base64(&s)).transpose()
211    }
212}
213
214/// A repeated `bytes` field, encoded as base64 strings.
215pub mod vec_bytes {
216    use super::*;
217
218    /// Emits each element as standard padded base64.
219    pub fn serialize<S: Serializer>(v: &[Vec<u8>], s: S) -> Result<S::Ok, S::Error> {
220        use serde::ser::SerializeSeq;
221        let mut seq = s.serialize_seq(Some(v.len()))?;
222        for item in v {
223            seq.serialize_element(&STANDARD.encode(item))?;
224        }
225        seq.end()
226    }
227
228    /// Accepts a sequence of base64 strings.
229    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<Vec<u8>>, D::Error> {
230        let raw = Option::<Vec<String>>::deserialize(d)?.unwrap_or_default();
231        raw.iter().map(|s| decode_base64(s)).collect()
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use serde::{Deserialize, Serialize};
238
239    #[derive(Debug, PartialEq, Serialize, Deserialize)]
240    struct Sample {
241        #[serde(
242            default,
243            with = "super::opt_int",
244            skip_serializing_if = "Option::is_none"
245        )]
246        seq: Option<i64>,
247        #[serde(
248            default,
249            with = "super::opt_bytes",
250            skip_serializing_if = "Option::is_none"
251        )]
252        data: Option<Vec<u8>>,
253        #[serde(
254            default,
255            with = "super::vec_int",
256            skip_serializing_if = "Vec::is_empty"
257        )]
258        counts: Vec<u64>,
259    }
260
261    #[test]
262    fn accepts_string_and_numeric_integers() {
263        let from_string: Sample = serde_json::from_str(r#"{"seq":"17"}"#).unwrap();
264        let from_number: Sample = serde_json::from_str(r#"{"seq":17}"#).unwrap();
265        assert_eq!(from_string.seq, Some(17));
266        assert_eq!(from_number.seq, from_string.seq);
267    }
268
269    #[test]
270    fn emits_the_canonical_string_form() {
271        let s = Sample {
272            seq: Some(-3),
273            data: None,
274            counts: vec![1, 2],
275        };
276        assert_eq!(
277            serde_json::to_string(&s).unwrap(),
278            r#"{"seq":"-3","counts":["1","2"]}"#
279        );
280    }
281
282    #[test]
283    fn absent_fields_stay_absent() {
284        let s: Sample = serde_json::from_str("{}").unwrap();
285        assert_eq!(
286            s,
287            Sample {
288                seq: None,
289                data: None,
290                counts: vec![]
291            }
292        );
293    }
294
295    #[test]
296    fn base64_round_trips_across_alphabets() {
297        let padded: Sample = serde_json::from_str(r#"{"data":"//79"}"#).unwrap();
298        let url_safe: Sample = serde_json::from_str(r#"{"data":"__79"}"#).unwrap();
299        assert_eq!(padded.data, Some(vec![0xff, 0xfe, 0xfd]));
300        assert_eq!(url_safe.data, padded.data);
301        assert_eq!(
302            serde_json::to_string(&padded).unwrap(),
303            r#"{"data":"//79"}"#
304        );
305    }
306
307    #[test]
308    fn null_decodes_as_absent() {
309        let s: Sample = serde_json::from_str(r#"{"seq":null,"data":null}"#).unwrap();
310        assert_eq!(s.seq, None);
311        assert_eq!(s.data, None);
312    }
313}