iridium_stomp/frame.rs
1use std::fmt;
2
3/// A simple representation of a STOMP frame.
4///
5/// `Frame` contains the command (e.g. "SEND", "MESSAGE"), an ordered list
6/// of headers (key/value pairs) and the raw body bytes.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct Frame {
9 /// STOMP command (e.g. CONNECT, SEND, SUBSCRIBE)
10 pub command: String,
11 /// Ordered headers as (key, value) pairs
12 pub headers: Vec<(String, String)>,
13 /// Raw body bytes
14 pub body: Vec<u8>,
15}
16
17impl Frame {
18 /// Create a new frame with the given command and empty headers/body.
19 ///
20 /// Parameters
21 /// - `command`: the STOMP command name (for example, `"SEND"` or
22 /// `"SUBSCRIBE"`). Accepts any type convertible into `String`.
23 pub fn new(command: impl Into<String>) -> Self {
24 Self {
25 command: command.into(),
26 headers: Vec::new(),
27 body: Vec::new(),
28 }
29 }
30
31 /// Add a header (builder style).
32 ///
33 /// Parameters
34 /// - `key`: header name (converted to `String`).
35 /// - `value`: header value (converted to `String`).
36 ///
37 /// Returns the mutated `Frame` allowing builder-style chaining.
38 pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
39 self.headers.push((key.into(), value.into()));
40 self
41 }
42
43 /// Set the frame body (builder style).
44 ///
45 /// Parameters
46 /// - `body`: raw body bytes. Accepts any type convertible into `Vec<u8>`.
47 ///
48 /// Returns the mutated `Frame` allowing builder-style chaining.
49 pub fn set_body(mut self, body: impl Into<Vec<u8>>) -> Self {
50 self.body = body.into();
51 self
52 }
53
54 /// Request a receipt for this frame (builder style).
55 ///
56 /// When sent, the server will respond with a RECEIPT frame containing
57 /// the same receipt ID.
58 ///
59 /// Use this only with `Connection::send_frame`, which sends the frame
60 /// as-is. `Connection::send_frame_with_receipt` and
61 /// `Connection::send_frame_confirmed` add a generated receipt header of
62 /// their own and track it for you.
63 ///
64 /// Parameters
65 /// - `id`: the receipt identifier. Must be unique per connection.
66 ///
67 /// Returns the mutated `Frame` allowing builder-style chaining.
68 ///
69 /// # Example
70 ///
71 /// ```ignore
72 /// let frame = Frame::new("SEND")
73 /// .header("destination", "/queue/test")
74 /// .receipt("msg-001")
75 /// .set_body(b"hello".to_vec());
76 /// ```
77 pub fn receipt(self, id: impl Into<String>) -> Self {
78 self.header("receipt", id)
79 }
80
81 /// Get the value of a header by name.
82 ///
83 /// Returns the first header value matching the given key (case-insensitive),
84 /// or `None` if no such header exists.
85 pub fn get_header(&self, key: &str) -> Option<&str> {
86 self.headers
87 .iter()
88 .find(|(k, _)| k.eq_ignore_ascii_case(key))
89 .map(|(_, v)| v.as_str())
90 }
91}
92
93impl fmt::Display for Frame {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 writeln!(f, "Command: {}", self.command)?;
96 for (k, v) in &self.headers {
97 writeln!(f, "{}: {}", k, v)?;
98 }
99 writeln!(f, "Body ({} bytes)", self.body.len())
100 }
101}