use crate::protocol::{ClientCommand, Frame};
use std::collections::HashMap;
use uuid::Uuid;
macro_rules! default_frame {
($struct:ident($($initname:ident),*) => $($method:ident ($($names:ident),+)),*) => {
#[derive(Debug, Clone)]
pub struct $struct {
pub(crate) headers: HashMap<String, String>
}
impl $struct {
pub fn new($($initname: impl Into<String>),*) -> Self {
let mut headers = HashMap::new();
$(headers.insert(stringify!($initname).to_string().replace('_', "-"), $initname.into());)*
Self {
headers
}
}
$(pub fn $method(self, $($names: impl Into<String>),+) -> Self {{
let mut current = self;
$(current = current.header(stringify!($names).to_string().replace('_', "-"), $names);)+
current
}})*
pub fn header<A: Into<String>, B: Into<String>>(mut self, key: A, value: B) -> Self {
self.headers.insert(key.into(), value.into());
self
}
}
impl Into<Frame<ClientCommand>> for $struct {
fn into(self) -> Frame<ClientCommand> {
Frame {
command: ClientCommand::$struct,
headers: self.headers,
body: "".to_string()
}
}
}
}
}
default_frame!(Nack(id) => transaction (transaction), receipt(receipt));
default_frame!(Ack(id) => transaction (transaction), receipt(receipt));
default_frame!(Connect(accept_version, host) => );
impl Connect {
pub fn heartbeat(self, client_interval: u32, server_interval: u32) -> Self {
self.header(
"heart-beat",
format!("{},{}", client_interval, server_interval),
)
}
}
#[derive(Debug, Clone)]
pub struct Send {
headers: HashMap<String, String>,
payload: String,
}
impl Send {
pub fn new<A: Into<String>>(destination: A) -> Self {
let mut headers = HashMap::new();
headers.insert("destination".to_string(), destination.into());
Send {
headers,
payload: "".to_string(),
}
}
pub fn body<A: Into<String>>(mut self, payload: A) -> Self {
self.payload = payload.into();
self
}
pub fn receipt<A: Into<String>>(self, receipt_id: A) -> Self {
self.header("receipt", receipt_id)
}
pub fn header<A: Into<String>, B: Into<String>>(mut self, key: A, value: B) -> Self {
self.headers.insert(key.into(), value.into());
self
}
}
impl Into<Frame<ClientCommand>> for Send {
fn into(self) -> Frame<ClientCommand> {
Frame {
command: ClientCommand::Send,
headers: self.headers,
body: self.payload,
}
}
}
default_frame!(Subscribe(id, destination) => receipt (receipt));
impl Subscribe {
pub fn new_with_random_id<A: Into<String>>(destination: A) -> Self {
Self::new(Uuid::new_v4().to_string(), destination.into())
}
pub fn ack(self, ack: AckMode) -> Self {
self.header(
"ack",
match ack {
AckMode::Auto => "auto",
AckMode::Client => "client",
AckMode::ClientIndividual => "client-individual",
},
)
}
}
#[derive(Copy, Clone)]
pub enum AckMode {
Auto,
Client,
ClientIndividual,
}
default_frame!(Unsubscribe(id) => receipt (receipt));
default_frame!(Begin(transaction) => receipt (receipt));
default_frame!(Commit(transaction) => receipt (receipt));
default_frame!(Abort(transaction) => receipt (receipt));
default_frame!(Disconnect(transaction) => receipt (receipt));
#[cfg(test)]
mod tests {
use crate::protocol::frame::{Ack, Connect, Nack, Send, Subscribe};
use crate::protocol::{ClientCommand, Frame};
#[test]
fn test_ack() {
let ack_id = "12345";
let test_header = "random";
let test_value = "54321";
let frame: Frame<ClientCommand> = Ack::new(ack_id.to_owned())
.header(test_header.to_owned(), test_value.to_owned())
.into();
assert_eq!(frame.command, ClientCommand::Ack);
assert_eq!(frame.headers["id"], ack_id);
assert_eq!(frame.headers[test_header], test_value);
}
#[test]
fn test_nack() {
let nack_id = "12345";
let test_header = "random";
let test_value = "54321";
let frame: Frame<ClientCommand> = Nack::new(nack_id.to_owned())
.header(test_header.to_owned(), test_value.to_owned())
.into();
assert_eq!(frame.command, ClientCommand::Nack);
assert_eq!(frame.headers["id"], nack_id);
assert_eq!(frame.headers[test_header], test_value);
}
#[test]
fn test_connect() {
let accept_version = "1.2";
let client_heartbeat = 10;
let server_heartbeat = 25;
let host = "test-host";
let test_header = "random";
let test_value = "54321";
let frame: Frame<ClientCommand> = Connect::new(accept_version.to_owned(), host.to_owned())
.heartbeat(client_heartbeat, server_heartbeat)
.header(test_header.to_owned(), test_value.to_owned())
.into();
assert_eq!(frame.command, ClientCommand::Connect);
assert_eq!(frame.headers["accept-version"], accept_version);
assert_eq!(frame.headers["host"], host);
assert_eq!(
frame.headers["heart-beat"],
format!("{},{}", client_heartbeat, server_heartbeat)
);
assert_eq!(frame.headers[test_header], test_value);
}
#[test]
fn test_send() {
let destination = "/dest/123";
let body = "test-payload";
let test_header = "random";
let test_value = "54321";
let frame: Frame<ClientCommand> = Send::new(destination.to_owned())
.body(body.to_owned())
.header(test_header.to_owned(), test_value.to_owned())
.into();
assert_eq!(frame.command, ClientCommand::Send);
assert_eq!(frame.headers["destination"], destination);
assert_eq!(frame.body, body);
assert_eq!(frame.headers[test_header], test_value);
}
#[test]
fn test_subscribe() {
let subscribe_id = "12345";
let destination = "/dest/123";
let receipt_id = "receipt-123";
let test_header = "random";
let test_value = "54321";
let frame: Frame<ClientCommand> =
Subscribe::new(subscribe_id.to_owned(), destination.to_owned())
.receipt(receipt_id.to_owned())
.header(test_header.to_owned(), test_value.to_owned())
.into();
assert_eq!(frame.command, ClientCommand::Subscribe);
assert_eq!(frame.headers["id"], subscribe_id);
assert_eq!(frame.headers["destination"], destination);
assert_eq!(frame.headers["receipt"], receipt_id);
assert_eq!(frame.headers[test_header], test_value);
}
}