stomp_rs/protocol/
frame.rs

1use crate::protocol::{ClientCommand, Frame};
2use std::collections::HashMap;
3use uuid::Uuid;
4
5macro_rules! default_frame {
6    ($struct:ident($($initname:ident),*) => $($method:ident ($($names:ident),+)),*) => {
7        #[derive(Debug, Clone)]
8        pub struct $struct {
9            pub(crate) headers: HashMap<String, String>
10        }
11
12        impl $struct {
13            pub fn new($($initname: impl Into<String>),*) -> Self {
14                let mut headers = HashMap::new();
15                $(headers.insert(stringify!($initname).to_string().replace('_', "-"), $initname.into());)*
16
17                Self {
18                    headers
19                }
20            }
21
22            $(pub fn $method(self, $($names: impl Into<String>),+) -> Self {{
23                let mut current = self;
24                $(current = current.header(stringify!($names).to_string().replace('_', "-"), $names);)+
25
26                current
27            }})*
28
29            pub fn header<A: Into<String>, B: Into<String>>(mut self, key: A, value: B) -> Self {
30                self.headers.insert(key.into(), value.into());
31
32                self
33            }
34        }
35
36        impl Into<Frame<ClientCommand>> for $struct {
37            fn into(self) -> Frame<ClientCommand> {
38                Frame {
39                    command: ClientCommand::$struct,
40                    headers: self.headers,
41                    body: "".to_string()
42                }
43            }
44        }
45    }
46}
47
48default_frame!(Nack(id) => transaction (transaction), receipt(receipt));
49default_frame!(Ack(id) => transaction (transaction), receipt(receipt));
50default_frame!(Connect(accept_version, host) => );
51
52impl Connect {
53    pub fn heartbeat(self, client_interval: u32, server_interval: u32) -> Self {
54        self.header(
55            "heart-beat",
56            format!("{},{}", client_interval, server_interval),
57        )
58    }
59}
60
61#[derive(Debug, Clone)]
62pub struct Send {
63    headers: HashMap<String, String>,
64    payload: String,
65}
66
67impl Send {
68    pub fn new<A: Into<String>>(destination: A) -> Self {
69        let mut headers = HashMap::new();
70        headers.insert("destination".to_string(), destination.into());
71
72        Send {
73            headers,
74            payload: "".to_string(),
75        }
76    }
77
78    pub fn body<A: Into<String>>(mut self, payload: A) -> Self {
79        self.payload = payload.into();
80
81        self
82    }
83
84    pub fn receipt<A: Into<String>>(self, receipt_id: A) -> Self {
85        self.header("receipt", receipt_id)
86    }
87
88    pub fn header<A: Into<String>, B: Into<String>>(mut self, key: A, value: B) -> Self {
89        self.headers.insert(key.into(), value.into());
90
91        self
92    }
93}
94
95impl Into<Frame<ClientCommand>> for Send {
96    fn into(self) -> Frame<ClientCommand> {
97        Frame {
98            command: ClientCommand::Send,
99            headers: self.headers,
100            body: self.payload,
101        }
102    }
103}
104
105default_frame!(Subscribe(id, destination) => receipt (receipt));
106impl Subscribe {
107    pub fn new_with_random_id<A: Into<String>>(destination: A) -> Self {
108        Self::new(Uuid::new_v4().to_string(), destination.into())
109    }
110
111    pub fn ack(self, ack: AckMode) -> Self {
112        self.header(
113            "ack",
114            match ack {
115                AckMode::Auto => "auto",
116                AckMode::Client => "client",
117                AckMode::ClientIndividual => "client-individual",
118            },
119        )
120    }
121}
122
123#[derive(Copy, Clone)]
124pub enum AckMode {
125    Auto,
126    Client,
127    ClientIndividual,
128}
129
130default_frame!(Unsubscribe(id) => receipt (receipt));
131default_frame!(Begin(transaction) => receipt (receipt));
132default_frame!(Commit(transaction) => receipt (receipt));
133default_frame!(Abort(transaction) => receipt (receipt));
134default_frame!(Disconnect(transaction) => receipt (receipt));
135
136#[cfg(test)]
137mod tests {
138    use crate::protocol::frame::{Ack, Connect, Nack, Send, Subscribe};
139    use crate::protocol::{ClientCommand, Frame};
140
141    #[test]
142    fn test_ack() {
143        let ack_id = "12345";
144        let test_header = "random";
145        let test_value = "54321";
146
147        let frame: Frame<ClientCommand> = Ack::new(ack_id.to_owned())
148            .header(test_header.to_owned(), test_value.to_owned())
149            .into();
150
151        assert_eq!(frame.command, ClientCommand::Ack);
152        assert_eq!(frame.headers["id"], ack_id);
153        assert_eq!(frame.headers[test_header], test_value);
154    }
155
156    #[test]
157    fn test_nack() {
158        let nack_id = "12345";
159        let test_header = "random";
160        let test_value = "54321";
161
162        let frame: Frame<ClientCommand> = Nack::new(nack_id.to_owned())
163            .header(test_header.to_owned(), test_value.to_owned())
164            .into();
165
166        assert_eq!(frame.command, ClientCommand::Nack);
167        assert_eq!(frame.headers["id"], nack_id);
168        assert_eq!(frame.headers[test_header], test_value);
169    }
170
171    #[test]
172    fn test_connect() {
173        let accept_version = "1.2";
174        let client_heartbeat = 10;
175        let server_heartbeat = 25;
176        let host = "test-host";
177        let test_header = "random";
178        let test_value = "54321";
179
180        let frame: Frame<ClientCommand> = Connect::new(accept_version.to_owned(), host.to_owned())
181            .heartbeat(client_heartbeat, server_heartbeat)
182            .header(test_header.to_owned(), test_value.to_owned())
183            .into();
184
185        assert_eq!(frame.command, ClientCommand::Connect);
186        assert_eq!(frame.headers["accept-version"], accept_version);
187        assert_eq!(frame.headers["host"], host);
188        assert_eq!(
189            frame.headers["heart-beat"],
190            format!("{},{}", client_heartbeat, server_heartbeat)
191        );
192        assert_eq!(frame.headers[test_header], test_value);
193    }
194
195    #[test]
196    fn test_send() {
197        let destination = "/dest/123";
198        let body = "test-payload";
199        let test_header = "random";
200        let test_value = "54321";
201
202        let frame: Frame<ClientCommand> = Send::new(destination.to_owned())
203            .body(body.to_owned())
204            .header(test_header.to_owned(), test_value.to_owned())
205            .into();
206
207        assert_eq!(frame.command, ClientCommand::Send);
208        assert_eq!(frame.headers["destination"], destination);
209        assert_eq!(frame.body, body);
210        assert_eq!(frame.headers[test_header], test_value);
211    }
212
213    #[test]
214    fn test_subscribe() {
215        let subscribe_id = "12345";
216        let destination = "/dest/123";
217        let receipt_id = "receipt-123";
218        let test_header = "random";
219        let test_value = "54321";
220
221        let frame: Frame<ClientCommand> =
222            Subscribe::new(subscribe_id.to_owned(), destination.to_owned())
223                .receipt(receipt_id.to_owned())
224                .header(test_header.to_owned(), test_value.to_owned())
225                .into();
226
227        assert_eq!(frame.command, ClientCommand::Subscribe);
228        assert_eq!(frame.headers["id"], subscribe_id);
229        assert_eq!(frame.headers["destination"], destination);
230        assert_eq!(frame.headers["receipt"], receipt_id);
231        assert_eq!(frame.headers[test_header], test_value);
232    }
233}