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. Use this with `Connection::wait_for_receipt()`
58    /// to confirm delivery.
59    ///
60    /// Parameters
61    /// - `id`: the receipt identifier. Must be unique per connection.
62    ///
63    /// Returns the mutated `Frame` allowing builder-style chaining.
64    ///
65    /// # Example
66    ///
67    /// ```ignore
68    /// let frame = Frame::new("SEND")
69    ///     .header("destination", "/queue/test")
70    ///     .receipt("msg-001")
71    ///     .set_body(b"hello".to_vec());
72    /// ```
73    pub fn receipt(self, id: impl Into<String>) -> Self {
74        self.header("receipt", id)
75    }
76
77    /// Get the value of a header by name.
78    ///
79    /// Returns the first header value matching the given key (case-sensitive),
80    /// or `None` if no such header exists.
81    pub fn get_header(&self, key: &str) -> Option<&str> {
82        self.headers
83            .iter()
84            .find(|(k, _)| k == key)
85            .map(|(_, v)| v.as_str())
86    }
87}
88
89impl fmt::Display for Frame {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        writeln!(f, "Command: {}", self.command)?;
92        for (k, v) in &self.headers {
93            writeln!(f, "{}: {}", k, v)?;
94        }
95        writeln!(f, "Body ({} bytes)", self.body.len())
96    }
97}