Skip to main content

wireframe/message_assembler/
types.rs

1//! Public types for message assembly inputs and outputs.
2//!
3//! This module contains `FirstFrameInput`, `FirstFrameInputError`,
4//! `EnvelopeRouting`, and `AssembledMessage`, extracted from `state.rs`
5//! to meet the 400-line file limit.
6
7use thiserror::Error;
8
9use super::{FirstFrameHeader, MessageKey};
10
11/// Envelope identifier from the enclosing transport frame.
12#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
13pub struct EnvelopeId(pub u32);
14
15impl From<u32> for EnvelopeId {
16    fn from(value: u32) -> Self { Self(value) }
17}
18
19impl From<EnvelopeId> for u32 {
20    fn from(value: EnvelopeId) -> Self { value.0 }
21}
22
23/// Correlation identifier from the enclosing transport frame.
24#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
25pub struct CorrelationId(pub u64);
26
27impl From<u64> for CorrelationId {
28    fn from(value: u64) -> Self { Self(value) }
29}
30
31impl From<CorrelationId> for u64 {
32    fn from(value: CorrelationId) -> Self { value.0 }
33}
34
35/// Routing metadata from the transport envelope that carried a first frame.
36///
37/// Captured at first-frame time so the completed [`AssembledMessage`] can
38/// be dispatched to the correct handler and logged under the original
39/// correlation identifier, regardless of which continuation frame
40/// completed the assembly.
41#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
42pub struct EnvelopeRouting {
43    /// Envelope identifier from the enclosing transport frame.
44    pub envelope_id: EnvelopeId,
45    /// Correlation identifier from the enclosing transport frame.
46    pub correlation_id: Option<CorrelationId>,
47}
48
49/// Input data for a first frame.
50///
51/// Groups the header and payload components that comprise a first frame,
52/// along with the [`EnvelopeRouting`] metadata so the assembler can
53/// preserve it for the completed message.
54///
55/// # Examples
56///
57/// ```
58/// use wireframe::message_assembler::{
59///     CorrelationId,
60///     EnvelopeId,
61///     EnvelopeRouting,
62///     FirstFrameHeader,
63///     FirstFrameInput,
64///     MessageKey,
65/// };
66///
67/// let header = FirstFrameHeader {
68///     message_key: MessageKey(1),
69///     metadata_len: 2,
70///     body_len: 5,
71///     total_body_len: None,
72///     is_last: false,
73/// };
74/// let routing = EnvelopeRouting {
75///     envelope_id: EnvelopeId(42),
76///     correlation_id: Some(CorrelationId(7)),
77/// };
78/// let input = FirstFrameInput::new(&header, routing, vec![0x01, 0x02], b"hello")
79///     .expect("header lengths match payload sizes");
80/// assert_eq!(input.header.message_key, MessageKey(1));
81/// assert_eq!(input.routing.envelope_id, EnvelopeId(42));
82/// ```
83#[derive(Debug)]
84pub struct FirstFrameInput<'a> {
85    /// The frame header.
86    pub header: &'a FirstFrameHeader,
87    /// Envelope routing metadata from the enclosing transport frame.
88    pub routing: EnvelopeRouting,
89    /// Protocol-specific metadata.
90    pub metadata: Vec<u8>,
91    /// Body payload slice.
92    pub body: &'a [u8],
93}
94
95/// Error returned when [`FirstFrameInput`] validation fails.
96#[derive(Clone, Copy, Debug, PartialEq, Eq, Error)]
97pub enum FirstFrameInputError {
98    /// Metadata length in header does not match actual metadata size.
99    #[error("metadata length mismatch: header declares {header_len} bytes, got {actual_len}")]
100    MetadataLengthMismatch {
101        /// Length declared in header.
102        header_len: usize,
103        /// Actual length of metadata slice.
104        actual_len: usize,
105    },
106    /// Body length in header does not match actual body size.
107    #[error("body length mismatch: header declares {header_len} bytes, got {actual_len}")]
108    BodyLengthMismatch {
109        /// Length declared in header.
110        header_len: usize,
111        /// Actual length of body slice.
112        actual_len: usize,
113    },
114}
115
116impl<'a> FirstFrameInput<'a> {
117    /// Create a new first frame input, validating header lengths against payloads.
118    ///
119    /// # Errors
120    ///
121    /// Returns an error if `header.metadata_len` does not match `metadata.len()`
122    /// or `header.body_len` does not match `body.len()`.
123    pub fn new(
124        header: &'a FirstFrameHeader,
125        routing: EnvelopeRouting,
126        metadata: Vec<u8>,
127        body: &'a [u8],
128    ) -> Result<Self, FirstFrameInputError> {
129        if header.metadata_len != metadata.len() {
130            return Err(FirstFrameInputError::MetadataLengthMismatch {
131                header_len: header.metadata_len,
132                actual_len: metadata.len(),
133            });
134        }
135        if header.body_len != body.len() {
136            return Err(FirstFrameInputError::BodyLengthMismatch {
137                header_len: header.body_len,
138                actual_len: body.len(),
139            });
140        }
141        Ok(Self {
142            header,
143            routing,
144            metadata,
145            body,
146        })
147    }
148}
149
150/// Container for a fully assembled message.
151///
152/// Preserves [`EnvelopeRouting`] from the first frame so that completed
153/// messages are dispatched to the correct handler and logged under the
154/// original correlation identifier.
155///
156/// # Examples
157///
158/// ```
159/// use wireframe::message_assembler::{
160///     AssembledMessage,
161///     CorrelationId,
162///     EnvelopeId,
163///     EnvelopeRouting,
164///     MessageKey,
165/// };
166///
167/// // Normally obtained from MessageAssemblyState::accept_first_frame or
168/// // accept_continuation_frame when a message completes.
169/// let routing = EnvelopeRouting {
170///     envelope_id: EnvelopeId(42),
171///     correlation_id: Some(CorrelationId(7)),
172/// };
173/// let msg = AssembledMessage::new(MessageKey(1), routing, vec![0x01], vec![0x02, 0x03]);
174/// assert_eq!(msg.message_key(), MessageKey(1));
175/// assert_eq!(msg.routing().envelope_id, EnvelopeId(42));
176/// assert_eq!(msg.routing().correlation_id, Some(CorrelationId(7)));
177/// assert_eq!(msg.metadata(), &[0x01]);
178/// assert_eq!(msg.body(), &[0x02, 0x03]);
179/// ```
180#[derive(Clone, Debug, PartialEq, Eq)]
181pub struct AssembledMessage {
182    message_key: MessageKey,
183    routing: EnvelopeRouting,
184    metadata: Vec<u8>,
185    body: Vec<u8>,
186}
187
188impl AssembledMessage {
189    /// Create a new assembled message.
190    #[must_use]
191    pub fn new(
192        message_key: MessageKey,
193        routing: EnvelopeRouting,
194        metadata: Vec<u8>,
195        body: Vec<u8>,
196    ) -> Self {
197        Self {
198            message_key,
199            routing,
200            metadata,
201            body,
202        }
203    }
204
205    /// Message key that correlated the frames.
206    #[must_use]
207    pub const fn message_key(&self) -> MessageKey { self.message_key }
208
209    /// Envelope routing metadata from the first frame.
210    #[must_use]
211    pub const fn routing(&self) -> EnvelopeRouting { self.routing }
212
213    /// Protocol-specific metadata from the first frame.
214    #[must_use]
215    pub fn metadata(&self) -> &[u8] { &self.metadata }
216
217    /// Reassembled body bytes.
218    #[must_use]
219    pub fn body(&self) -> &[u8] { &self.body }
220
221    /// Consume and return the body bytes.
222    #[must_use]
223    pub fn into_body(self) -> Vec<u8> { self.body }
224}