Skip to main content

trillium_grpc/codec/
prost_codec.rs

1use crate::{Codec, Status};
2use bytes::{Bytes, BytesMut};
3use prost::Message;
4
5/// The default protobuf codec, backed by [`prost`].
6pub struct Prost;
7
8impl<T> Codec<T> for Prost
9where
10    T: Message + Default + 'static,
11{
12    fn content_type_suffix() -> &'static str {
13        "proto"
14    }
15
16    fn encode(value: &T) -> Result<Bytes, Status> {
17        let mut buf = BytesMut::with_capacity(value.encoded_len());
18        value
19            .encode(&mut buf)
20            .map_err(|e| Status::internal(format!("prost encode failed: {e}")))?;
21        Ok(buf.freeze())
22    }
23
24    fn decode(bytes: &[u8]) -> Result<T, Status> {
25        T::decode(bytes).map_err(|e| Status::invalid_argument(format!("prost decode failed: {e}")))
26    }
27}
28
29#[cfg(test)]
30mod tests {
31    use super::*;
32
33    // Use a built-in prost Message impl (Vec<u8>) to avoid needing prost-build.
34    // For the protobuf wire format, a bytes field at tag 1 encodes to:
35    //   tag (1<<3 | 2 = 0x0A) + varint(len) + payload
36    // For an empty Vec<u8>, prost omits the field entirely.
37
38    #[test]
39    fn prost_codec_suffix() {
40        assert_eq!(<Prost as Codec<Vec<u8>>>::content_type_suffix(), "proto");
41    }
42
43    #[test]
44    fn prost_codec_encode_empty() {
45        let value: Vec<u8> = Vec::new();
46        let encoded = <Prost as Codec<Vec<u8>>>::encode(&value).unwrap();
47        assert!(encoded.is_empty());
48    }
49
50    #[test]
51    fn prost_codec_decode_empty() {
52        let decoded: Vec<u8> = <Prost as Codec<Vec<u8>>>::decode(&[]).unwrap();
53        assert!(decoded.is_empty());
54    }
55
56    #[test]
57    fn prost_codec_decode_garbage_is_invalid_argument() {
58        // 0xFF is not a valid prost tag — high continuation bit, never terminates.
59        let err = <Prost as Codec<Vec<u8>>>::decode(&[0xFF, 0xFF, 0xFF, 0xFF]).unwrap_err();
60        assert_eq!(err.code, crate::Code::InvalidArgument);
61    }
62}