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}