Skip to main content

structured_proxy/transcode/
codec.rs

1//! Dynamic gRPC codec for `prost-reflect::DynamicMessage`.
2//!
3//! Allows sending/receiving protobuf messages without compile-time type information,
4//! using `MessageDescriptor` for runtime encoding/decoding.
5
6use prost::bytes::Buf;
7use prost::Message;
8use prost_reflect::{DynamicMessage, MessageDescriptor};
9use tonic::codec::{BufferSettings, Codec, DecodeBuf, Decoder, EncodeBuf, Encoder};
10use tonic::Status;
11
12/// Encoder for `DynamicMessage` → wire bytes.
13#[derive(Debug, Clone)]
14pub struct DynamicEncoder;
15
16impl Encoder for DynamicEncoder {
17    type Item = DynamicMessage;
18    type Error = Status;
19
20    fn encode(&mut self, item: Self::Item, buf: &mut EncodeBuf<'_>) -> Result<(), Status> {
21        item.encode(buf)
22            .map_err(|e| Status::internal(format!("encode error: {e}")))
23    }
24
25    fn buffer_settings(&self) -> BufferSettings {
26        BufferSettings::default()
27    }
28}
29
30/// Decoder for wire bytes → `DynamicMessage`.
31#[derive(Debug, Clone)]
32pub struct DynamicDecoder {
33    desc: MessageDescriptor,
34}
35
36impl DynamicDecoder {
37    pub fn new(desc: MessageDescriptor) -> Self {
38        Self { desc }
39    }
40}
41
42impl Decoder for DynamicDecoder {
43    type Item = DynamicMessage;
44    type Error = Status;
45
46    fn decode(&mut self, buf: &mut DecodeBuf<'_>) -> Result<Option<Self::Item>, Status> {
47        let remaining = buf.remaining();
48        if remaining == 0 {
49            return Ok(None);
50        }
51        let msg = DynamicMessage::decode(self.desc.clone(), buf.copy_to_bytes(remaining))
52            .map_err(|e| Status::internal(format!("decode error: {e}")))?;
53        Ok(Some(msg))
54    }
55
56    fn buffer_settings(&self) -> BufferSettings {
57        BufferSettings::default()
58    }
59}
60
61/// Codec that encodes/decodes `DynamicMessage` using runtime descriptors.
62#[derive(Debug, Clone)]
63pub struct DynamicCodec {
64    response_desc: MessageDescriptor,
65}
66
67impl DynamicCodec {
68    pub fn new(response_desc: MessageDescriptor) -> Self {
69        Self { response_desc }
70    }
71}
72
73impl Codec for DynamicCodec {
74    type Encode = DynamicMessage;
75    type Decode = DynamicMessage;
76    type Encoder = DynamicEncoder;
77    type Decoder = DynamicDecoder;
78
79    fn encoder(&mut self) -> Self::Encoder {
80        DynamicEncoder
81    }
82
83    fn decoder(&mut self) -> Self::Decoder {
84        DynamicDecoder::new(self.response_desc.clone())
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    #[test]
91    fn test_dynamic_codec_creation() {
92        // Use google.protobuf.Empty as a universal test message
93        let pool = prost_reflect::DescriptorPool::decode(
94            prost_reflect::DescriptorPool::global()
95                .encode_to_vec()
96                .as_slice(),
97        )
98        .unwrap_or_else(|_| prost_reflect::DescriptorPool::new());
99        // Basic smoke test — codec can be created with any message descriptor
100        let _ = pool;
101    }
102}