Skip to main content

microsandbox_protocol_client/
message.rs

1//! Native, encoded-payload, and inspectable inbound message surfaces.
2
3use microsandbox_protocol::wire::{self, Envelope};
4use serde::{Serialize, de::DeserializeOwned};
5
6use crate::{ClientError, ClientResult, EnvelopeCodec, ErrorKind, Protocol, RawFrame};
7
8//--------------------------------------------------------------------------------------------------
9// Types
10//--------------------------------------------------------------------------------------------------
11
12/// Decoded message with its original frame retained for unknown fields/forwarding.
13pub struct Message {
14    /// Actual envelope generation.
15    pub v: u8,
16    /// Actual wire name, including unknown future names.
17    pub t: String,
18    /// Frame correlation ID.
19    pub id: u32,
20    /// Actual frame flags.
21    pub flags: u8,
22    /// Original encoded payload.
23    pub p: Vec<u8>,
24    frame: RawFrame,
25}
26
27/// Native payload paired with an explicit wire name; this is not schema proof.
28pub struct TypedMessage<T> {
29    /// Application wire name.
30    pub message_type: String,
31    /// Native serializable value, often a borrowed prepared request.
32    pub payload: T,
33}
34
35/// Already-encoded application payload, separate from the outer envelope.
36pub struct EncodedMessage {
37    /// Application wire name.
38    pub message_type: String,
39    /// Exact CBOR payload bytes; never normalized.
40    pub payload: Vec<u8>,
41}
42
43/// Complete envelope and flags, before the router assigns a correlation ID.
44pub struct OutboundMessage {
45    /// Protocol-selected flags.
46    pub flags: u8,
47    /// Opaque envelope bytes.
48    pub body: Vec<u8>,
49}
50
51/// Prepare a named message using one protocol's availability gates and codec.
52pub trait IntoOutboundMessage<P: Protocol> {
53    /// Validate before admission and encode only the layer supplied by the caller.
54    fn into_outbound(
55        self,
56        ready: &P::Ready,
57        codec: &dyn EnvelopeCodec,
58    ) -> ClientResult<OutboundMessage>;
59}
60
61//--------------------------------------------------------------------------------------------------
62// Methods
63//--------------------------------------------------------------------------------------------------
64
65impl Message {
66    /// Construct a view for a custom envelope codec without losing original bytes.
67    pub fn new(frame: RawFrame, envelope: Envelope) -> Self {
68        Self {
69            v: envelope.v,
70            t: envelope.t,
71            id: frame.id,
72            flags: frame.flags,
73            p: envelope.p,
74            frame,
75        }
76    }
77
78    /// Decode a known payload without narrowing the incoming message namespace.
79    pub fn payload<T: DeserializeOwned>(&self) -> ClientResult<T> {
80        let value = wire::decode_value(&self.p)?;
81        value
82            .deserialized()
83            .map_err(|_| ClientError::new(ErrorKind::InvalidData))
84    }
85
86    /// Inspect the original frame, including unknown envelope fields.
87    pub fn raw(&self) -> &RawFrame {
88        &self.frame
89    }
90
91    /// Recover the exact received frame for forwarding.
92    pub fn into_raw(self) -> RawFrame {
93        self.frame
94    }
95}
96
97impl<T> TypedMessage<T> {
98    /// Pair a native payload with a wire name without performing I/O.
99    pub fn new(message_type: impl AsRef<str>, payload: T) -> Self {
100        Self {
101            message_type: message_type.as_ref().into(),
102            payload,
103        }
104    }
105}
106
107impl EncodedMessage {
108    /// Pair opaque payload bytes with a wire name without encoding them again.
109    pub fn new(message_type: impl AsRef<str>, payload: impl Into<Vec<u8>>) -> Self {
110        Self {
111            message_type: message_type.as_ref().into(),
112            payload: payload.into(),
113        }
114    }
115}
116
117//--------------------------------------------------------------------------------------------------
118// Trait Implementations
119//--------------------------------------------------------------------------------------------------
120
121impl<P: Protocol, T: Serialize> IntoOutboundMessage<P> for TypedMessage<T> {
122    fn into_outbound(
123        self,
124        ready: &P::Ready,
125        codec: &dyn EnvelopeCodec,
126    ) -> ClientResult<OutboundMessage> {
127        let metadata = P::prepare(ready, &self.message_type)?;
128        let payload =
129            wire::encode(&self.payload).map_err(|_| ClientError::new(ErrorKind::Encode))?;
130        let body = codec.encode(metadata.generation, &self.message_type, payload)?;
131        Ok(OutboundMessage {
132            flags: metadata.flags,
133            body,
134        })
135    }
136}
137
138impl<P: Protocol> IntoOutboundMessage<P> for EncodedMessage {
139    fn into_outbound(
140        self,
141        ready: &P::Ready,
142        codec: &dyn EnvelopeCodec,
143    ) -> ClientResult<OutboundMessage> {
144        let metadata = P::prepare(ready, &self.message_type)?;
145        let body = codec.encode(metadata.generation, &self.message_type, self.payload)?;
146        Ok(OutboundMessage {
147            flags: metadata.flags,
148            body,
149        })
150    }
151}
152
153impl std::fmt::Debug for Message {
154    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155        f.debug_struct("Message")
156            .field("id", &self.id)
157            .field("flags", &self.flags)
158            .field("generation", &self.v)
159            .field("payload_bytes", &self.p.len())
160            .finish_non_exhaustive()
161    }
162}