mesh_protobuf/table/
mod.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Table-based message encoding and decoding.
5//!
6//! Instead of directly implementing message encode and decode, this code
7//! implements encoding and decoding by walking a table of field metadata
8//! (offsets, field numbers, and function pointers).
9//!
10//! This has more compact code generation than the direct implementation.
11
12pub mod decode;
13pub mod encode;
14mod tuple;
15
16use crate::protofile::DescribeField;
17use crate::protofile::DescribeMessage;
18use crate::protofile::FieldType;
19use crate::protofile::MessageDescription;
20
21/// A message encoder/decoder that uses tables associated with the message type.
22pub struct TableEncoder;
23
24/// A table-encoded type that has a protobuf message description.
25#[diagnostic::on_unimplemented(
26    message = "`{Self}` is not a stable protobuf type",
27    label = "`{Self}` does not have a mesh package name",
28    note = "consider adding `#[mesh(package = \"my.package.name\")]` to the type"
29)]
30pub trait DescribeTable {
31    /// The protobuf message description for this type.
32    const DESCRIPTION: MessageDescription<'static>;
33}
34
35impl<T: DescribeTable> DescribeMessage<T> for TableEncoder {
36    const DESCRIPTION: MessageDescription<'static> = T::DESCRIPTION;
37}
38
39impl<T: DescribeTable> DescribeField<T> for TableEncoder {
40    const FIELD_TYPE: FieldType<'static> = FieldType::message(|| T::DESCRIPTION);
41}
42
43/// Trait for types that can be encoded using [`TableEncoder`].
44///
45/// # Safety
46///
47/// The implementor must ensure that this metadata describes all fields within
48/// `Self`, and that `OFFSETS` are the correct byte offsets to the fields.
49pub unsafe trait StructMetadata {
50    /// The field numbers for each field.
51    const NUMBERS: &'static [u32]; // TODO: make a compact version of this, perhaps
52    /// The byte offset to each field within the struct.
53    const OFFSETS: &'static [usize]; // TODO: u32 (or even u16?)
54}
55
56#[cfg(test)]
57#[expect(clippy::undocumented_unsafe_blocks)]
58mod tests {
59    use super::StructMetadata;
60    use super::TableEncoder;
61    use super::decode::ErasedDecoderEntry;
62    use super::decode::StructDecodeMetadata;
63    use super::encode::ErasedEncoderEntry;
64    use super::encode::StructEncodeMetadata;
65    use crate::FieldDecode;
66    use crate::FieldEncode;
67    use crate::encoding::StringField;
68    use crate::encoding::VarintField;
69    use crate::tests::as_expect_str;
70    use core::mem::offset_of;
71    use expect_test::expect;
72
73    #[derive(PartialEq, Eq, Debug)]
74    struct Foo<'a> {
75        a: u32,
76        b: u64,
77        x: &'a str,
78    }
79
80    unsafe impl<'a> StructMetadata for Foo<'a> {
81        const NUMBERS: &'static [u32] = &[1, 2, 3];
82        const OFFSETS: &'static [usize] = &[
83            offset_of!(Foo<'a>, a),
84            offset_of!(Foo<'a>, b),
85            offset_of!(Foo<'a>, x),
86        ];
87    }
88    unsafe impl<'a, R> StructEncodeMetadata<R> for Foo<'a> {
89        const ENCODERS: &'static [ErasedEncoderEntry] = &[
90            <VarintField as FieldEncode<u32, R>>::ENTRY.erase(),
91            <VarintField as FieldEncode<u64, R>>::ENTRY.erase(),
92            <StringField as FieldEncode<&'a str, R>>::ENTRY.erase(),
93        ];
94    }
95    unsafe impl<'de, R> StructDecodeMetadata<'de, R> for Foo<'de> {
96        const DECODERS: &'static [ErasedDecoderEntry] = &[
97            <VarintField as FieldDecode<'de, u32, R>>::ENTRY.erase(),
98            <VarintField as FieldDecode<'de, u64, R>>::ENTRY.erase(),
99            <StringField as FieldDecode<'de, &'de str, R>>::ENTRY.erase(),
100        ];
101    }
102    impl crate::DefaultEncoding for Foo<'_> {
103        type Encoding = TableEncoder;
104    }
105
106    #[test]
107    fn test_derived_macro() {
108        let data = crate::encode(Foo {
109            a: 1,
110            b: 2,
111            x: "hi",
112        });
113        let expected = expect!([r#"
114            1: varint 1
115            2: varint 2
116            3: string "hi"
117            raw: 080110021a026869"#]);
118        expected.assert_eq(&as_expect_str(&data));
119        let foo = crate::decode::<Foo<'_>>(&data).unwrap();
120        assert_eq!(foo.a, 1);
121        assert_eq!(foo.b, 2);
122        assert_eq!(foo.x, "hi");
123    }
124}