mesh_protobuf/
message.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Type-erased protobuf message support.
5
6use crate::DefaultEncoding;
7use crate::DescribedProtobuf;
8use crate::Error;
9use crate::MessageDecode;
10use crate::MessageEncode;
11use crate::Protobuf;
12use crate::decode;
13use crate::encode;
14use crate::encoding::MessageEncoding;
15use crate::inplace::InplaceOption;
16use crate::protobuf::MessageReader;
17use crate::protobuf::MessageSizer;
18use crate::protobuf::MessageWriter;
19use crate::protofile::DescribeField;
20use crate::protofile::FieldType;
21use crate::protofile::MessageDescription;
22use crate::table::DescribeTable;
23use alloc::string::String;
24use alloc::string::ToString;
25use alloc::vec::Vec;
26use thiserror::Error;
27
28/// An opaque protobuf message.
29//
30// TODO: delay encoding like in mesh::Message. This requires splitting some of
31// the encoding traits up to remove the resource type.
32#[derive(Debug)]
33pub struct ProtobufMessage(Vec<u8>);
34
35impl ProtobufMessage {
36    /// Encodes `data` as a protobuf message.
37    pub fn new(data: impl Protobuf) -> Self {
38        Self(encode(data))
39    }
40
41    /// Decodes the protobuf message into `T`.
42    pub fn parse<T: Protobuf>(&self) -> Result<T, Error> {
43        decode(&self.0)
44    }
45}
46
47impl DefaultEncoding for ProtobufMessage {
48    type Encoding = MessageEncoding<ProtobufMessageEncoding>;
49}
50
51impl DescribeField<ProtobufMessage> for MessageEncoding<ProtobufMessageEncoding> {
52    const FIELD_TYPE: FieldType<'static> = FieldType::builtin("bytes");
53}
54
55/// Encoder for [`ProtobufMessage`].
56#[derive(Debug)]
57pub struct ProtobufMessageEncoding;
58
59impl<R> MessageEncode<ProtobufMessage, R> for ProtobufMessageEncoding {
60    fn write_message(item: ProtobufMessage, mut writer: MessageWriter<'_, '_, R>) {
61        writer.bytes(&item.0);
62    }
63
64    fn compute_message_size(item: &mut ProtobufMessage, mut sizer: MessageSizer<'_>) {
65        sizer.bytes(item.0.len());
66    }
67}
68
69impl<R> MessageDecode<'_, ProtobufMessage, R> for ProtobufMessageEncoding {
70    fn read_message(
71        item: &mut InplaceOption<'_, ProtobufMessage>,
72        reader: MessageReader<'_, '_, R>,
73    ) -> crate::Result<()> {
74        item.get_or_insert_with(|| ProtobufMessage(Vec::new()))
75            .0
76            .extend(reader.bytes());
77        Ok(())
78    }
79}
80
81/// A protobuf message and the associated protobuf type URL.
82///
83/// This has the encoding of `google.protobuf.Any`.
84#[derive(Debug, Protobuf)]
85pub struct ProtobufAny {
86    #[mesh(1)]
87    type_url: String, // FUTURE: avoid allocation here
88    #[mesh(2)]
89    value: ProtobufMessage,
90}
91
92#[derive(Debug, Error)]
93#[error("protobuf type mismatch, expected {expected}, got {actual}")]
94struct TypeMismatch {
95    expected: String,
96    actual: String,
97}
98
99impl DescribeTable for ProtobufAny {
100    const DESCRIPTION: MessageDescription<'static> = MessageDescription::External {
101        name: "google.protobuf.Any",
102        import_path: "google/protobuf/any.proto",
103    };
104}
105
106impl ProtobufAny {
107    /// Encodes `data` as a protobuf message.
108    pub fn new<T: DescribedProtobuf>(data: T) -> Self {
109        Self {
110            type_url: T::TYPE_URL.to_string(),
111            value: ProtobufMessage::new(data),
112        }
113    }
114
115    /// Decodes the protobuf message into `T`.
116    ///
117    /// Fails if this message is an encoding of a different type.
118    pub fn parse<T: DescribedProtobuf>(&self) -> Result<T, Error> {
119        if &T::TYPE_URL != self.type_url.as_str() {
120            return Err(Error::new(TypeMismatch {
121                expected: T::TYPE_URL.to_string(),
122                actual: self.type_url.clone(),
123            }));
124        }
125        self.value.parse()
126    }
127
128    /// Returns `true` if this message is an encoding of `T`.
129    pub fn is_message<T: DescribedProtobuf>(&self) -> bool {
130        &T::TYPE_URL == self.type_url.as_str()
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    extern crate std;
137
138    use crate::Protobuf;
139    use crate::encode;
140    use crate::message::ProtobufAny;
141    use crate::message::ProtobufMessage;
142    use crate::tests::as_expect_str;
143    use expect_test::expect;
144    use std::println;
145
146    #[test]
147    fn test_message() {
148        let message = (5u32,);
149
150        // Round trips.
151        assert_eq!(
152            ProtobufMessage::new(message).parse::<(u32,)>().unwrap(),
153            message
154        );
155
156        let expected = expect!([r#"
157            1: varint 5
158            raw: 0805"#]);
159        let actual = encode(ProtobufMessage::new(message));
160        expected.assert_eq(&as_expect_str(&actual));
161
162        // Is transparent.
163        assert_eq!(actual, encode(message));
164    }
165
166    #[test]
167    fn test_any() {
168        #[derive(Protobuf, PartialEq, Eq, Copy, Clone, Debug)]
169        #[mesh(package = "test")]
170        struct Message {
171            #[mesh(1)]
172            x: u32,
173        }
174
175        #[derive(Protobuf, Debug)]
176        #[mesh(package = "test")]
177        struct Other {
178            #[mesh(1)]
179            x: u32,
180        }
181
182        let msg = Message { x: 5 };
183        let any = ProtobufAny::new(msg);
184
185        assert_eq!(any.type_url, "type.googleapis.com/test.Message");
186        assert!(any.is_message::<Message>());
187        assert!(!any.is_message::<Other>());
188        assert_eq!(any.parse::<Message>().unwrap(), msg);
189        println!("{:?}", any.parse::<Other>().unwrap_err());
190    }
191}