Skip to main content

ts_wire/
codec.rs

1// Copyright (c) 2026 LightSeek Foundation
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! The [`Wire`] trait and the struct layouts msgspec uses.
22//!
23//! msgspec encodes a `Struct` in one of two layouts, both mirrored here by
24//! [`wire_struct!`]:
25//!
26//! * **array** (`array_like=True`): `[tag?, field, ...]` in declaration
27//!   order. Declaration order is the wire contract, so fields are only ever
28//!   appended. A shorter array takes the remaining fields' defaults; a
29//!   longer one has its extra trailing elements ignored. A tagged struct
30//!   (`tag=True`) starts with its class name.
31//! * **map**: `{name: value, ...}` with every field present, in declaration
32//!   order. Unknown keys are ignored and missing keys take their defaults.
33//!
34//! Both layouts fail when a field without a default is absent.
35
36use bytes::Bytes;
37
38use crate::{
39    error::{Error, Result},
40    msgpack::{Decoder, Encoder, Kind},
41};
42
43/// A type with a msgpack encoding shared with the Python side.
44pub trait Wire: Sized {
45    /// Append this value to a message.
46    fn encode(&self, encoder: &mut Encoder);
47
48    /// Read one value of this type.
49    fn decode(decoder: &mut Decoder<'_>) -> Result<Self>;
50}
51
52/// Encode a value as a message: the msgpack buffer, then its out-of-band
53/// frames.
54pub fn encode_frames<T: Wire>(value: &T) -> Vec<Bytes> {
55    let mut encoder = Encoder::new();
56    value.encode(&mut encoder);
57    encoder.into_frames()
58}
59
60/// Decode a message from its frames, failing on bytes after the value.
61pub fn decode_frames<T: Wire>(frames: &[Bytes]) -> Result<T> {
62    let mut decoder = Decoder::new(frames);
63    let value = T::decode(&mut decoder)?;
64    decoder.finish()?;
65    Ok(value)
66}
67
68/// Decode a message that is a single msgpack buffer.
69pub fn decode_bytes<T: Wire>(bytes: &[u8]) -> Result<T> {
70    decode_frames(&[Bytes::copy_from_slice(bytes)])
71}
72
73impl Wire for bool {
74    fn encode(&self, encoder: &mut Encoder) {
75        encoder.bool(*self);
76    }
77
78    fn decode(decoder: &mut Decoder<'_>) -> Result<Self> {
79        decoder.bool()
80    }
81}
82
83macro_rules! wire_int {
84    ($($ty:ty => $write:ident),* $(,)?) => {$(
85        impl Wire for $ty {
86            fn encode(&self, encoder: &mut Encoder) {
87                encoder.$write((*self).into());
88            }
89
90            fn decode(decoder: &mut Decoder<'_>) -> Result<Self> {
91                decoder.integer()?.narrow(stringify!($ty))
92            }
93        }
94    )*};
95}
96
97wire_int!(i32 => int, i64 => int, u16 => uint, u32 => uint, u64 => uint);
98
99impl Wire for f64 {
100    fn encode(&self, encoder: &mut Encoder) {
101        encoder.float(*self);
102    }
103
104    fn decode(decoder: &mut Decoder<'_>) -> Result<Self> {
105        decoder.float()
106    }
107}
108
109impl Wire for String {
110    fn encode(&self, encoder: &mut Encoder) {
111        encoder.str(self);
112    }
113
114    fn decode(decoder: &mut Decoder<'_>) -> Result<Self> {
115        Ok(decoder.str()?.to_owned())
116    }
117}
118
119/// `None` is `nil` on the wire.
120impl<T: Wire> Wire for Option<T> {
121    fn encode(&self, encoder: &mut Encoder) {
122        match self {
123            Some(value) => value.encode(encoder),
124            None => encoder.nil(),
125        }
126    }
127
128    fn decode(decoder: &mut Decoder<'_>) -> Result<Self> {
129        if decoder.take_nil()? {
130            Ok(None)
131        } else {
132            T::decode(decoder).map(Some)
133        }
134    }
135}
136
137/// Lists, tuples of any length and sets are all arrays on the wire.
138impl<T: Wire> Wire for Vec<T> {
139    fn encode(&self, encoder: &mut Encoder) {
140        encoder.array_len(self.len());
141        for item in self {
142            item.encode(encoder);
143        }
144    }
145
146    fn decode(decoder: &mut Decoder<'_>) -> Result<Self> {
147        let len = decoder.array_len()?;
148        // Bound the up-front reservation: the length is untrusted input.
149        let mut items = Vec::with_capacity(len.min(1 << 16));
150        for _ in 0..len {
151            items.push(T::decode(decoder)?);
152        }
153        Ok(items)
154    }
155}
156
157/// A two-element tuple, an array of length two on the wire.
158impl<A: Wire, B: Wire> Wire for (A, B) {
159    fn encode(&self, encoder: &mut Encoder) {
160        encoder.array_len(2);
161        self.0.encode(encoder);
162        self.1.encode(encoder);
163    }
164
165    fn decode(decoder: &mut Decoder<'_>) -> Result<Self> {
166        let len = decoder.array_len()?;
167        if len != 2 {
168            return Err(Error::Invalid(format!(
169                "expected a 2-element array, found {len} elements"
170            )));
171        }
172        Ok((A::decode(decoder)?, B::decode(decoder)?))
173    }
174}
175
176/// A Python `dict`: a msgpack map whose entries keep their wire order.
177#[derive(Debug, Clone, PartialEq)]
178pub struct Dict<K, V>(pub Vec<(K, V)>);
179
180impl<K, V> Default for Dict<K, V> {
181    fn default() -> Self {
182        Dict(Vec::new())
183    }
184}
185
186impl<K: PartialEq, V> Dict<K, V> {
187    /// The value for `key`, if present.
188    pub fn get<Q>(&self, key: &Q) -> Option<&V>
189    where
190        K: PartialEq<Q>,
191        Q: ?Sized,
192    {
193        self.0.iter().find(|(k, _)| k == key).map(|(_, v)| v)
194    }
195}
196
197impl<K: Wire, V: Wire> Wire for Dict<K, V> {
198    fn encode(&self, encoder: &mut Encoder) {
199        encoder.map_len(self.0.len());
200        for (key, value) in &self.0 {
201            key.encode(encoder);
202            value.encode(encoder);
203        }
204    }
205
206    fn decode(decoder: &mut Decoder<'_>) -> Result<Self> {
207        let len = decoder.map_len()?;
208        let mut entries = Vec::with_capacity(len.min(1 << 16));
209        for _ in 0..len {
210            let key = K::decode(decoder)?;
211            entries.push((key, V::decode(decoder)?));
212        }
213        Ok(Dict(entries))
214    }
215}
216
217/// A Python `str | list[str]` field.
218#[derive(Debug, Clone, PartialEq, Eq)]
219pub enum StrOrList {
220    /// A single string.
221    One(String),
222    /// A list of strings.
223    Many(Vec<String>),
224}
225
226impl Wire for StrOrList {
227    fn encode(&self, encoder: &mut Encoder) {
228        match self {
229            StrOrList::One(value) => value.encode(encoder),
230            StrOrList::Many(values) => values.encode(encoder),
231        }
232    }
233
234    fn decode(decoder: &mut Decoder<'_>) -> Result<Self> {
235        match decoder.peek_kind()? {
236            Kind::Str => Ok(StrOrList::One(String::decode(decoder)?)),
237            Kind::Array => Ok(StrOrList::Many(Vec::decode(decoder)?)),
238            _ => Err(Error::Invalid(
239                "expected a string or a list of strings".to_owned(),
240            )),
241        }
242    }
243}
244
245/// Reads the fields of an array-layout struct in declaration order.
246#[doc(hidden)]
247pub struct ArrayFields<'d, 'f> {
248    decoder: &'d mut Decoder<'f>,
249    remaining: usize,
250}
251
252impl<'d, 'f> ArrayFields<'d, 'f> {
253    /// Read the array header.
254    pub fn begin(decoder: &'d mut Decoder<'f>) -> Result<Self> {
255        let remaining = decoder.array_len()?;
256        Ok(Self { decoder, remaining })
257    }
258
259    /// Check the tag a tagged struct starts with.
260    pub fn tag(&mut self, expected: &'static str) -> Result<()> {
261        if self.remaining == 0 {
262            return Err(Error::MissingField("_tag"));
263        }
264        self.remaining -= 1;
265        let found = self.decoder.str()?;
266        if found != expected {
267            return Err(Error::Tag {
268                expected,
269                found: found.to_owned(),
270            });
271        }
272        Ok(())
273    }
274
275    /// The next field, or `None` once the array is exhausted.
276    pub fn next<T: Wire>(&mut self, name: &'static str) -> Result<Option<T>> {
277        if self.remaining == 0 {
278            return Ok(None);
279        }
280        self.remaining -= 1;
281        T::decode(self.decoder)
282            .map(Some)
283            .map_err(|error| error.in_field(name))
284    }
285
286    /// Skip trailing elements a newer peer appended.
287    pub fn finish(self) -> Result<()> {
288        for _ in 0..self.remaining {
289            self.decoder.skip()?;
290        }
291        Ok(())
292    }
293}
294
295/// Define a struct and its [`Wire`] codec in one of msgspec's layouts.
296///
297/// ```ignore
298/// wire_struct! {
299///     /// Doc.
300///     pub struct Example: array("Example") {
301///         /// A required field.
302///         pub name: String,
303///         /// A field with a default, used when an older peer omits it.
304///         pub count: i64 = 0,
305///     }
306/// }
307/// ```
308///
309/// The layout is `array` (optionally with a tag) or `map`. Writing
310/// `+ Default` after the layout also derives `Default` from the field
311/// defaults, which then must be given for every field.
312macro_rules! wire_struct {
313    (
314        $(#[$meta:meta])*
315        $vis:vis struct $name:ident: $layout:ident $(($tag:expr))? + Default {
316            $( $(#[$fmeta:meta])* $fvis:vis $field:ident: $fty:ty = $fdefault:expr ),* $(,)?
317        }
318    ) => {
319        wire_struct! {
320            $(#[$meta])*
321            $vis struct $name: $layout $(($tag))? {
322                $( $(#[$fmeta])* $fvis $field: $fty = $fdefault ),*
323            }
324        }
325
326        impl Default for $name {
327            fn default() -> Self {
328                Self { $( $field: $fdefault ),* }
329            }
330        }
331    };
332    (
333        $(#[$meta:meta])*
334        $vis:vis struct $name:ident: $layout:ident $(($tag:expr))? {
335            $( $(#[$fmeta:meta])* $fvis:vis $field:ident: $fty:ty $(= $fdefault:expr)? ),* $(,)?
336        }
337    ) => {
338        $(#[$meta])*
339        $vis struct $name {
340            $( $(#[$fmeta])* $fvis $field: $fty ),*
341        }
342
343        wire_struct!(@$layout $name $(($tag))? { $( $field: $fty $(= $fdefault)? ),* });
344    };
345    (@array $name:ident $(($tag:expr))? { $( $field:ident: $fty:ty $(= $fdefault:expr)? ),* }) => {
346        impl $crate::codec::Wire for $name {
347            fn encode(&self, encoder: &mut $crate::msgpack::Encoder) {
348                let tag_len: usize = 0 $( + { let _ = $tag; 1 } )?;
349                encoder.array_len(tag_len $( + { let _ = stringify!($field); 1 } )*);
350                $( encoder.str($tag); )?
351                $( $crate::codec::Wire::encode(&self.$field, encoder); )*
352            }
353
354            fn decode(
355                decoder: &mut $crate::msgpack::Decoder<'_>,
356            ) -> $crate::error::Result<Self> {
357                let mut fields = $crate::codec::ArrayFields::begin(decoder)?;
358                $( fields.tag($tag)?; )?
359                $(
360                    let $field: $fty = match fields.next(stringify!($field))? {
361                        Some(value) => value,
362                        None => wire_struct!(@default $field $($fdefault)?),
363                    };
364                )*
365                fields.finish()?;
366                Ok(Self { $( $field ),* })
367            }
368        }
369    };
370    (@map $name:ident { $( $field:ident: $fty:ty $(= $fdefault:expr)? ),* }) => {
371        impl $crate::codec::Wire for $name {
372            fn encode(&self, encoder: &mut $crate::msgpack::Encoder) {
373                encoder.map_len(0 $( + { let _ = stringify!($field); 1 } )*);
374                $(
375                    encoder.str(stringify!($field));
376                    $crate::codec::Wire::encode(&self.$field, encoder);
377                )*
378            }
379
380            fn decode(
381                decoder: &mut $crate::msgpack::Decoder<'_>,
382            ) -> $crate::error::Result<Self> {
383                $( let mut $field: Option<$fty> = None; )*
384                for _ in 0..decoder.map_len()? {
385                    match decoder.str()? {
386                        $(
387                            stringify!($field) => {
388                                $field = Some(
389                                    $crate::codec::Wire::decode(decoder)
390                                        .map_err(|error| error.in_field(stringify!($field)))?,
391                                );
392                            }
393                        )*
394                        _ => decoder.skip()?,
395                    }
396                }
397                Ok(Self {
398                    $(
399                        $field: match $field {
400                            Some(value) => value,
401                            None => wire_struct!(@default $field $($fdefault)?),
402                        },
403                    )*
404                })
405            }
406        }
407    };
408    (@default $field:ident) => {
409        return Err($crate::error::Error::MissingField(stringify!($field)))
410    };
411    (@default $field:ident $default:expr) => {
412        $default
413    };
414}
415
416#[cfg(test)]
417mod tests {
418    use super::*;
419
420    wire_struct! {
421        /// A tagged array struct with an appended defaulted field.
422        #[derive(Debug, PartialEq)]
423        struct Tagged: array("Tagged") {
424            name: String,
425            count: i64 = 5,
426        }
427    }
428
429    wire_struct! {
430        /// A map struct.
431        #[derive(Debug, PartialEq)]
432        struct Named: map + Default {
433            x: i64 = 1,
434            y: Option<String> = None,
435        }
436    }
437
438    fn encode_value(write: impl FnOnce(&mut Encoder)) -> Vec<u8> {
439        let mut encoder = Encoder::new();
440        write(&mut encoder);
441        encoder.into_single_frame()
442    }
443
444    #[test]
445    fn short_arrays_take_defaults_and_long_arrays_drop_extras() {
446        let short = encode_value(|e| {
447            e.array_len(2);
448            e.str("Tagged");
449            e.str("a");
450        });
451        let decoded: Tagged = decode_bytes(&short).unwrap();
452        assert_eq!(
453            decoded,
454            Tagged {
455                name: "a".into(),
456                count: 5
457            }
458        );
459
460        let long = encode_value(|e| {
461            e.array_len(4);
462            e.str("Tagged");
463            e.str("a");
464            e.int(9);
465            e.str("from a newer peer");
466        });
467        let decoded: Tagged = decode_bytes(&long).unwrap();
468        assert_eq!(
469            decoded,
470            Tagged {
471                name: "a".into(),
472                count: 9
473            }
474        );
475    }
476
477    #[test]
478    fn required_fields_and_tags_are_enforced() {
479        let missing = encode_value(|e| {
480            e.array_len(1);
481            e.str("Tagged");
482        });
483        assert!(matches!(
484            decode_bytes::<Tagged>(&missing),
485            Err(Error::MissingField("name"))
486        ));
487
488        let wrong_tag = encode_value(|e| {
489            e.array_len(2);
490            e.str("Other");
491            e.str("a");
492        });
493        assert!(matches!(
494            decode_bytes::<Tagged>(&wrong_tag),
495            Err(Error::Tag { .. })
496        ));
497    }
498
499    #[test]
500    fn map_structs_ignore_unknown_keys_and_default_missing_ones() {
501        let bytes = encode_value(|e| {
502            e.map_len(2);
503            e.str("unknown");
504            e.array_len(1);
505            e.nil();
506            e.str("y");
507            e.str("v");
508        });
509        let decoded: Named = decode_bytes(&bytes).unwrap();
510        assert_eq!(
511            decoded,
512            Named {
513                x: 1,
514                y: Some("v".into())
515            }
516        );
517
518        let encoded = encode_frames(&Named::default());
519        assert_eq!(&encoded[0][..], b"\x82\xa1x\x01\xa1y\xc0");
520    }
521
522    #[test]
523    fn errors_name_the_field() {
524        let bytes = encode_value(|e| {
525            e.array_len(3);
526            e.str("Tagged");
527            e.str("a");
528            e.str("not an int");
529        });
530        let error = decode_bytes::<Tagged>(&bytes).unwrap_err();
531        assert!(error.to_string().starts_with("field `count`:"), "{error}");
532    }
533}