use crate::error::PacketError;
use bytes::Bytes;
use bytestring::ByteString;
use miette::Diagnostic;
use serde::Deserialize;
use serde_json::{Map, Value};
use thiserror::Error;
use tokio::sync::{mpsc, oneshot};
#[derive(Debug)]
pub struct Ns<T>(pub ByteString, pub T);
#[derive(Debug, Deserialize)]
pub struct Connect {
pub sid: ByteString,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
#[derive(Debug, Error, Diagnostic, Deserialize)]
#[error("{message}")]
#[diagnostic(
code(sioc::connect_error),
help(
"Server rejected the namespace connection. Verify your auth payload and server middleware."
),
url("https://socket.io/docs/v4/socket-io-protocol/#connection-to-a-namespace")
)]
pub struct ConnectError {
pub message: ByteString,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
#[derive(Debug, Clone)]
pub struct DynEvent {
pub payload: ByteString,
pub id: Option<u64>,
pub attachments: Option<Vec<Bytes>>,
}
impl std::fmt::Display for DynEvent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut map = f.debug_map();
map.entry(&"payload", &format_args!("{}", self.payload));
if let Some(id) = self.id {
map.entry(&"id", &id);
}
if let Some(attachments) = &self.attachments {
map.entry(&"count", &attachments.len());
}
map.finish()
}
}
impl DynEvent {
pub fn new<T>(payload: T, id: Option<u64>) -> Self
where
T: Into<ByteString>,
{
Self {
payload: payload.into(),
id,
attachments: None,
}
}
#[must_use]
pub fn with_attachments(mut self, attachments: Vec<Bytes>) -> Self {
self.attachments = Some(attachments);
self
}
}
#[derive(Debug, Clone)]
pub struct DynAck {
pub payload: ByteString,
pub attachments: Option<Vec<Bytes>>,
}
impl std::fmt::Display for DynAck {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut map = f.debug_map();
map.entry(&"payload", &format_args!("{}", self.payload));
if let Some(attachments) = &self.attachments {
map.entry(&"count", &attachments.len());
}
map.finish()
}
}
impl DynAck {
pub fn new<T>(payload: T) -> Self
where
T: Into<ByteString>,
{
Self {
payload: payload.into(),
attachments: None,
}
}
#[must_use]
pub fn with_attachments(mut self, attachments: Vec<Bytes>) -> Self {
self.attachments = Some(attachments);
self
}
}
#[derive(Debug)]
pub enum Signal<E = DynEvent> {
Connect(Connect),
Disconnect,
ConnectError(ConnectError),
Event(E),
}
impl<E> std::fmt::Display for Signal<E>
where
E: std::fmt::Display,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Connect(c) => f
.debug_tuple("Connect")
.field(&format_args!("{}", c.sid))
.finish(),
Self::Disconnect => f.write_str("Disconnect"),
Self::ConnectError(e) => f
.debug_tuple("ConnectError")
.field(&format_args!("{e}"))
.finish(),
Self::Event(e) => f.debug_tuple("Event").field(&format_args!("{e}")).finish(),
}
}
}
impl<E> Signal<E> {
pub fn take_event(self) -> Option<E> {
match self {
Self::Event(e) => Some(e),
_ => None,
}
}
pub fn and_then<F, T>(self, f: F) -> Option<T>
where
F: FnOnce(E) -> Option<T>,
{
match self {
Self::Event(e) => f(e),
_ => None,
}
}
pub fn map<F, U>(self, f: F) -> Signal<U>
where
F: FnOnce(E) -> U,
{
match self {
Self::Connect(c) => Signal::Connect(c),
Self::Disconnect => Signal::Disconnect,
Self::ConnectError(e) => Signal::ConnectError(e),
Self::Event(e) => Signal::Event(f(e)),
}
}
}
#[derive(Debug)]
#[allow(missing_docs)]
pub enum Directive {
Connect {
tx: mpsc::Sender<Signal>,
payload: ByteString,
},
Disconnect,
Event {
payload: ByteString,
tx: Option<oneshot::Sender<DynAck>>,
attachments: Option<Vec<Bytes>>,
},
Ack {
payload: ByteString,
id: u64,
attachments: Option<Vec<Bytes>>,
},
Dropped,
}
#[derive(Debug)]
#[allow(missing_docs)]
pub enum Packet {
Connect(ByteString),
Disconnect,
Event {
payload: ByteString,
id: Option<u64>,
},
Ack { payload: ByteString, id: u64 },
ConnectError(ByteString),
BinaryEvent {
payload: ByteString,
id: Option<u64>,
count: usize,
},
BinaryAck {
payload: ByteString,
id: u64,
count: usize,
},
}
impl Packet {
pub fn size_hint(&self, ns: &str) -> usize {
match self {
Self::Connect(payload) | Self::ConnectError(payload) => {
hint_packet_size(ns, false, false, Some(payload))
}
Self::Disconnect => hint_packet_size(ns, false, false, None),
Self::Event { payload, id } => hint_packet_size(ns, false, id.is_some(), Some(payload)),
Self::Ack { payload, .. } => hint_packet_size(ns, false, true, Some(payload)),
Self::BinaryEvent { payload, id, .. } => {
hint_packet_size(ns, true, id.is_some(), Some(payload))
}
Self::BinaryAck { payload, .. } => hint_packet_size(ns, true, true, Some(payload)),
}
}
pub fn encode(&self, ns: &str) -> String {
let mut buffer = String::with_capacity(self.size_hint(ns));
match self {
Self::Connect(bytes) => {
write_packet(&mut buffer, b'0', None, ns, None, Some(bytes));
}
Self::Disconnect => {
write_packet(&mut buffer, b'1', None, ns, None, None);
}
Self::Event { payload, id } => {
write_packet(&mut buffer, b'2', None, ns, *id, Some(payload));
}
Self::Ack { payload, id } => {
write_packet(&mut buffer, b'3', None, ns, Some(*id), Some(payload));
}
Self::ConnectError(payload) => {
write_packet(&mut buffer, b'4', None, ns, None, Some(payload));
}
Self::BinaryEvent { payload, id, count } => {
write_packet(&mut buffer, b'5', Some(*count), ns, *id, Some(payload));
}
Self::BinaryAck { payload, id, count } => {
write_packet(
&mut buffer,
b'6',
Some(*count),
ns,
Some(*id),
Some(payload),
);
}
}
buffer
}
}
impl std::fmt::Display for Packet {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Connect(payload) => f
.debug_tuple("Connect")
.field(&format_args!("{payload}"))
.finish(),
Self::Disconnect => f.write_str("Disconnect"),
Self::Event { payload, id } => {
let mut s = f.debug_struct("Event");
s.field("payload", &format_args!("{payload}"));
if let Some(id) = id {
s.field("id", id);
}
s.finish()
}
Self::Ack { payload, id } => f
.debug_struct("Ack")
.field("payload", &format_args!("{payload}"))
.field("id", id)
.finish(),
Self::ConnectError(payload) => f
.debug_tuple("ConnectError")
.field(&format_args!("{payload}"))
.finish(),
Self::BinaryEvent { payload, id, count } => {
let mut s = f.debug_struct("BinaryEvent");
s.field("payload", &format_args!("{payload}"));
if let Some(id) = id {
s.field("id", id);
}
s.field("count", count);
s.finish()
}
Self::BinaryAck { payload, id, count } => f
.debug_struct("BinaryAck")
.field("payload", &format_args!("{payload}"))
.field("id", id)
.field("count", count)
.finish(),
}
}
}
impl Ns<Packet> {
pub fn size_hint(&self) -> usize {
self.1.size_hint(&self.0)
}
pub fn encode(&self) -> String {
self.1.encode(&self.0)
}
}
impl TryFrom<ByteString> for Ns<Packet> {
type Error = PacketError;
fn try_from(bytes: ByteString) -> Result<Self, PacketError> {
let mut chars = bytes.chars();
let id = chars.next().ok_or(PacketError::Empty)?;
let bytes = bytes.slice_ref(chars.as_str());
let packet = match id {
'0' => {
let (ns, payload) = split_namespace(bytes)?;
Ns(ns, Packet::Connect(payload))
}
'1' => {
let (ns, _) = split_namespace(bytes)?;
Ns(ns, Packet::Disconnect)
}
'2' => {
let (count, bytes) = split_attachments(bytes)?;
if let Some(count) = count {
return Err(PacketError::UnexpectedAttachments { count });
}
let (ns, bytes) = split_namespace(bytes)?;
let (id, payload) = split_id(bytes)?;
Ns(ns, Packet::Event { payload, id })
}
'3' => {
let (ns, bytes) = split_namespace(bytes)?;
let (id, payload) = split_id(bytes)?;
let id = id.ok_or(PacketError::MissingAckId)?;
Ns(ns, Packet::Ack { payload, id })
}
'4' => {
let (ns, payload) = split_namespace(bytes)?;
Ns(ns, Packet::ConnectError(payload))
}
'5' => {
let (count, bytes) = split_attachments(bytes)?;
let count = count.ok_or(PacketError::MissingAttachmentCount)?;
let (ns, bytes) = split_namespace(bytes)?;
let (id, payload) = split_id(bytes)?;
Ns(ns, Packet::BinaryEvent { payload, id, count })
}
'6' => {
let (count, bytes) = split_attachments(bytes)?;
let count = count.ok_or(PacketError::MissingAttachmentCount)?;
let (ns, bytes) = split_namespace(bytes)?;
let (id, payload) = split_id(bytes)?;
let id = id.ok_or(PacketError::MissingAckId)?;
Ns(ns, Packet::BinaryAck { payload, id, count })
}
id => return Err(PacketError::InvalidId { id }),
};
Ok(packet)
}
}
const U64_MAX_LEN: usize = 20;
const fn ack_size_hint() -> usize {
U64_MAX_LEN
}
const fn binary_size_hint() -> usize {
U64_MAX_LEN + 1
}
fn namespace_size(ns: &str) -> usize {
if ns == "/" { 0 } else { ns.len() + 1 }
}
pub(crate) fn hint_packet_size(ns: &str, binary: bool, ack: bool, payload: Option<&str>) -> usize {
let mut n = 1 + namespace_size(ns);
if ack {
n += ack_size_hint();
}
if binary {
n += binary_size_hint();
}
if let Some(payload) = payload {
n += payload.len();
}
n
}
fn write_attachments(buffer: &mut String, count: usize) {
buffer.push_str(&count.to_string());
buffer.push('-');
}
fn write_namespace(buffer: &mut String, ns: &str) {
if ns != "/" {
buffer.push_str(ns);
buffer.push(',');
}
}
fn write_id(buffer: &mut String, id: u64) {
buffer.push_str(&id.to_string());
}
fn write_payload(buffer: &mut String, payload: &str) {
buffer.push_str(payload);
}
pub(crate) fn write_packet(
buffer: &mut String,
type_id: u8,
count: Option<usize>,
ns: &str,
id: Option<u64>,
payload: Option<&str>,
) {
buffer.push(type_id as char);
if let Some(count) = count {
write_attachments(buffer, count);
}
write_namespace(buffer, ns);
if let Some(id) = id {
write_id(buffer, id);
}
if let Some(payload) = payload {
write_payload(buffer, payload);
}
}
pub fn split_attachments(bytes: ByteString) -> Result<(Option<usize>, ByteString), PacketError> {
let pair = match bytes.char_indices().find(|(_, c)| !c.is_ascii_digit()) {
Some((i, '-')) => {
let count = bytes[..i]
.parse()
.map_err(PacketError::InvalidAttachmentCount)?;
let rest = bytes.slice_ref(&bytes[i + 1..]);
(Some(count), rest)
}
_ => (None, bytes),
};
Ok(pair)
}
pub fn split_namespace(bytes: ByteString) -> Result<(ByteString, ByteString), PacketError> {
match bytes.chars().next() {
Some('/') => match bytes.split_once(',') {
Some((ns, payload)) => Ok((bytes.slice_ref(ns), bytes.slice_ref(payload))),
None => Err(PacketError::MissingNamespaceDelimiter),
},
_ => Ok((ByteString::from_static("/"), bytes)),
}
}
pub fn split_id(bytes: ByteString) -> Result<(Option<u64>, ByteString), PacketError> {
let pair = match bytes.char_indices().find(|(_, c)| !c.is_ascii_digit()) {
Some((i, _)) if i > 0 => {
let id = bytes[..i].parse().map_err(PacketError::InvalidAckId)?;
let rest = bytes.slice_ref(&bytes[i..]);
(Some(id), rest)
}
_ => (None, bytes),
};
Ok(pair)
}
#[cfg(test)]
mod tests {
use super::*;
use bytes::Bytes;
use bytestring::ByteString;
use serde_json::Map;
use crate::error::PacketError;
fn bss(s: &'static str) -> ByteString {
ByteString::from_static(s)
}
fn decode(s: &'static str) -> Result<Ns<Packet>, PacketError> {
bss(s).try_into()
}
fn encode_ns(ns: &'static str, pkt: Packet) -> (String, usize) {
let np = Ns(bss(ns), pkt);
let hint = np.size_hint();
(np.encode(), hint)
}
#[test]
fn split_attachments_empty_input() {
let (count, rest) = split_attachments(ByteString::new()).unwrap();
assert!(count.is_none());
assert!(rest.is_empty());
}
#[test]
fn split_attachments_no_prefix() {
let (count, rest) = split_attachments(bss("rest")).unwrap();
assert!(count.is_none());
assert_eq!(&*rest, "rest");
}
#[test]
fn split_attachments_valid_prefix() {
let (count, rest) = split_attachments(bss("2-rest")).unwrap();
assert_eq!(count, Some(2));
assert_eq!(&*rest, "rest");
}
#[test]
fn split_attachments_zero_count() {
let (count, rest) = split_attachments(bss("0-")).unwrap();
assert_eq!(count, Some(0));
assert!(rest.is_empty());
}
#[test]
fn split_attachments_namespace_not_misinterpreted() {
let (count, rest) = split_attachments(bss("/ns,data")).unwrap();
assert!(count.is_none());
assert_eq!(&*rest, "/ns,data");
}
#[test]
fn split_namespace_default_ns() {
let (ns, rest) = split_namespace(bss("payload")).unwrap();
assert_eq!(&*ns, "/");
assert_eq!(&*rest, "payload");
}
#[test]
fn split_namespace_non_default() {
let (ns, rest) = split_namespace(bss(r#"/chat,["x"]"#)).unwrap();
assert_eq!(&*ns, "/chat");
assert_eq!(&*rest, r#"["x"]"#);
}
#[test]
fn split_namespace_missing_delimiter() {
assert!(matches!(
split_namespace(bss("/bad")),
Err(PacketError::MissingNamespaceDelimiter)
));
}
#[test]
fn split_namespace_empty_payload_after_comma() {
let (ns, rest) = split_namespace(bss("/,")).unwrap();
assert_eq!(&*ns, "/");
assert!(rest.is_empty());
}
#[test]
fn split_id_empty() {
let (id, rest) = split_id(ByteString::new()).unwrap();
assert!(id.is_none());
assert!(rest.is_empty());
}
#[test]
fn split_id_no_leading_digits() {
let (id, rest) = split_id(bss(r#"["x"]"#)).unwrap();
assert!(id.is_none());
assert_eq!(&*rest, r#"["x"]"#);
}
#[test]
fn split_id_with_digits() {
let (id, rest) = split_id(bss(r#"7["x"]"#)).unwrap();
assert_eq!(id, Some(7));
assert_eq!(&*rest, r#"["x"]"#);
}
#[test]
fn split_id_all_digits_no_payload() {
let (id, rest) = split_id(bss("123")).unwrap();
assert!(id.is_none());
assert_eq!(&*rest, "123");
}
#[test]
fn decode_empty_is_error() {
assert!(matches!(decode(""), Err(PacketError::Empty)));
}
#[test]
fn decode_invalid_id_is_error() {
assert!(matches!(
decode("9"),
Err(PacketError::InvalidId { id: '9' })
));
}
#[test]
fn decode_connect_default_ns() {
let Ns(ns, pkt) = decode(r#"0{"sid":"s"}"#).unwrap();
assert_eq!(&*ns, "/");
assert!(matches!(pkt, Packet::Connect(_)));
}
#[test]
fn decode_connect_non_default_ns() {
let Ns(ns, pkt) = decode(r#"0/chat,{"sid":"s"}"#).unwrap();
assert_eq!(&*ns, "/chat");
assert!(matches!(pkt, Packet::Connect(_)));
}
#[test]
fn decode_disconnect() {
let Ns(ns, pkt) = decode("1").unwrap();
assert_eq!(&*ns, "/");
assert!(matches!(pkt, Packet::Disconnect));
}
#[test]
fn decode_event_no_id() {
let Ns(_, pkt) = decode(r#"2["evt"]"#).unwrap();
assert!(matches!(pkt, Packet::Event { id: None, .. }));
}
#[test]
fn decode_event_with_id() {
let Ns(_, pkt) = decode(r#"27["evt"]"#).unwrap();
assert!(matches!(pkt, Packet::Event { id: Some(7), .. }));
}
#[test]
fn decode_ack() {
let Ns(_, pkt) = decode(r#"37["ok"]"#).unwrap();
assert!(matches!(pkt, Packet::Ack { id: 7, .. }));
}
#[test]
fn decode_connect_error() {
let Ns(_, pkt) = decode(r#"4{"message":"bad"}"#).unwrap();
assert!(matches!(pkt, Packet::ConnectError(_)));
}
#[test]
fn decode_binary_event_no_id() {
let Ns(_, pkt) = decode(r#"52-["img"]"#).unwrap();
assert!(matches!(
pkt,
Packet::BinaryEvent {
count: 2,
id: None,
..
}
));
}
#[test]
fn decode_binary_event_with_id() {
let Ns(_, pkt) = decode(r#"52-3["img"]"#).unwrap();
assert!(matches!(
pkt,
Packet::BinaryEvent {
count: 2,
id: Some(3),
..
}
));
}
#[test]
fn decode_binary_ack() {
let Ns(_, pkt) = decode(r#"62-7["ok"]"#).unwrap();
assert!(matches!(
pkt,
Packet::BinaryAck {
count: 2,
id: 7,
..
}
));
}
#[test]
fn decode_ack_missing_id_is_error() {
assert!(matches!(
decode(r#"3["noid"]"#),
Err(PacketError::MissingAckId)
));
}
#[test]
fn decode_event_unexpected_attachments_is_error() {
assert!(matches!(
decode(r#"22-["x"]"#),
Err(PacketError::UnexpectedAttachments { count: 2 })
));
}
#[test]
fn decode_binary_event_missing_count_is_error() {
assert!(matches!(
decode(r#"5["x"]"#),
Err(PacketError::MissingAttachmentCount)
));
}
#[test]
fn decode_namespace_missing_delimiter_is_error() {
assert!(matches!(
decode(r#"0/chat["x"]"#),
Err(PacketError::MissingNamespaceDelimiter)
));
}
#[test]
fn encode_connect_default_ns() {
let (enc, hint) = encode_ns("/", Packet::Connect("{}".into()));
assert_eq!(enc, "0{}");
assert!(enc.len() <= hint);
}
#[test]
fn encode_connect_non_default_ns() {
let (enc, hint) = encode_ns("/chat", Packet::Connect("{}".into()));
assert_eq!(enc, "0/chat,{}");
assert!(enc.len() <= hint);
}
#[test]
fn encode_disconnect() {
let (enc, hint) = encode_ns("/", Packet::Disconnect);
assert_eq!(enc, "1");
assert!(enc.len() <= hint);
}
#[test]
fn encode_event_no_id() {
let (enc, hint) = encode_ns(
"/",
Packet::Event {
payload: r#"["evt"]"#.into(),
id: None,
},
);
assert_eq!(enc, r#"2["evt"]"#);
assert!(enc.len() <= hint);
}
#[test]
fn encode_event_with_id() {
let (enc, hint) = encode_ns(
"/",
Packet::Event {
payload: r#"["evt"]"#.into(),
id: Some(7),
},
);
assert_eq!(enc, r#"27["evt"]"#);
assert!(enc.len() <= hint);
}
#[test]
fn encode_ack() {
let (enc, hint) = encode_ns(
"/",
Packet::Ack {
payload: r#"["ok"]"#.into(),
id: 7,
},
);
assert_eq!(enc, r#"37["ok"]"#);
assert!(enc.len() <= hint);
}
#[test]
fn encode_connect_error() {
let (enc, hint) = encode_ns("/", Packet::ConnectError(r#"{"message":"bad"}"#.into()));
assert_eq!(enc, r#"4{"message":"bad"}"#);
assert!(enc.len() <= hint);
}
#[test]
fn encode_binary_event() {
let (enc, hint) = encode_ns(
"/",
Packet::BinaryEvent {
payload: r#"["img"]"#.into(),
id: None,
count: 2,
},
);
assert_eq!(enc, r#"52-["img"]"#);
assert!(enc.len() <= hint);
}
#[test]
fn encode_binary_ack() {
let (enc, hint) = encode_ns(
"/",
Packet::BinaryAck {
payload: r#"["ok"]"#.into(),
id: 7,
count: 2,
},
);
assert_eq!(enc, r#"62-7["ok"]"#);
assert!(enc.len() <= hint);
}
#[test]
fn dyn_event_new_has_no_attachments() {
let ev = DynEvent::new(r#"["ping"]"#, None);
assert!(ev.attachments.is_none());
assert!(ev.id.is_none());
}
#[test]
fn dyn_event_with_attachments() {
let ev =
DynEvent::new(r#"["ping"]"#, None).with_attachments(vec![Bytes::from_static(b"\x01")]);
assert_eq!(ev.attachments.as_ref().unwrap().len(), 1);
}
#[test]
fn dyn_event_display_shows_payload() {
let ev = DynEvent::new(r#"["ping"]"#, Some(3));
let s = format!("{ev}");
assert_eq!(s, r#"{"payload": ["ping"], "id": 3}"#);
}
#[test]
fn dyn_ack_new_has_no_attachments() {
let ack = DynAck::new("[true]");
assert!(ack.attachments.is_none());
}
#[test]
fn dyn_ack_with_attachments() {
let ack = DynAck::new("[true]").with_attachments(vec![Bytes::from_static(b"\x02")]);
assert_eq!(ack.attachments.as_ref().unwrap().len(), 1);
}
#[test]
fn dyn_ack_display_shows_payload() {
let ack = DynAck::new("[true]");
let s = format!("{ack}");
assert_eq!(s, r#"{"payload": [true]}"#);
}
fn make_connect() -> Connect {
Connect {
sid: bss("t"),
extra: Map::new(),
}
}
fn make_connect_error() -> ConnectError {
ConnectError {
message: bss("bad"),
extra: Map::new(),
}
}
#[test]
fn signal_take_event_returns_some() {
let sig = Signal::Event(DynEvent::new("[]", None));
assert!(sig.take_event().is_some());
}
#[test]
fn signal_take_event_on_non_event_returns_none() {
assert!(Signal::<u8>::Disconnect.take_event().is_none());
assert!(Signal::<u8>::Connect(make_connect()).take_event().is_none());
assert!(
Signal::<u8>::ConnectError(make_connect_error())
.take_event()
.is_none()
);
}
#[test]
fn signal_map_transforms_event() {
let sig: Signal<u8> = Signal::Event(3u8);
assert!(matches!(sig.map(|x| x * 2), Signal::Event(6)));
}
#[test]
fn signal_map_passes_non_event_through() {
assert!(matches!(
Signal::<u8>::Disconnect.map(|x: u8| x * 2),
Signal::Disconnect
));
assert!(matches!(
Signal::<u8>::Connect(make_connect()).map(|x: u8| x * 2),
Signal::Connect(_)
));
assert!(matches!(
Signal::<u8>::ConnectError(make_connect_error()).map(|x: u8| x * 2),
Signal::ConnectError(_)
));
}
#[test]
fn signal_and_then_returns_some_on_event() {
let sig: Signal<u8> = Signal::Event(3u8);
assert_eq!(
sig.and_then(|x| if x > 0 { Some(x) } else { None }),
Some(3)
);
}
#[test]
fn signal_and_then_returns_none_when_f_returns_none() {
let sig: Signal<u8> = Signal::Event(0u8);
assert!(
sig.and_then(|x| if x > 0 { Some(x) } else { None })
.is_none()
);
}
#[test]
fn signal_and_then_returns_none_on_non_event() {
assert!(Signal::<u8>::Disconnect.and_then(|x: u8| Some(x)).is_none());
assert!(
Signal::<u8>::Connect(make_connect())
.and_then(|x: u8| Some(x))
.is_none()
);
}
#[test]
fn signal_display_variants() {
assert_eq!(format!("{}", Signal::<u8>::Disconnect), "Disconnect");
let ev_sig = Signal::Event(DynEvent::new("[]", None));
assert_eq!(format!("{ev_sig}"), r#"Event({"payload": []})"#);
let connect_sig: Signal<u8> = Signal::Connect(make_connect());
assert_eq!(format!("{connect_sig}"), "Connect(t)");
let err_sig: Signal<u8> = Signal::ConnectError(make_connect_error());
assert_eq!(format!("{err_sig}"), "ConnectError(bad)");
}
#[test]
fn packet_display_variants() {
assert_eq!(format!("{}", Packet::Disconnect), "Disconnect");
assert_eq!(format!("{}", Packet::Connect("{}".into())), "Connect({})");
assert_eq!(
format!(
"{}",
Packet::Event {
payload: "[]".into(),
id: None
}
),
"Event { payload: [] }"
);
assert_eq!(
format!(
"{}",
Packet::Event {
payload: "[]".into(),
id: Some(3)
}
),
"Event { payload: [], id: 3 }"
);
assert_eq!(
format!(
"{}",
Packet::Ack {
payload: "[]".into(),
id: 5
}
),
"Ack { payload: [], id: 5 }"
);
assert_eq!(
format!("{}", Packet::ConnectError("{}".into())),
"ConnectError({})"
);
assert_eq!(
format!(
"{}",
Packet::BinaryEvent {
payload: "[]".into(),
id: None,
count: 2
}
),
"BinaryEvent { payload: [], count: 2 }"
);
assert_eq!(
format!(
"{}",
Packet::BinaryEvent {
payload: "[]".into(),
id: Some(3),
count: 2
}
),
"BinaryEvent { payload: [], id: 3, count: 2 }"
);
assert_eq!(
format!(
"{}",
Packet::BinaryAck {
payload: "[]".into(),
id: 5,
count: 2
}
),
"BinaryAck { payload: [], id: 5, count: 2 }"
);
}
#[test]
fn dyn_event_display_with_attachments() {
let ev = DynEvent::new("[]", None).with_attachments(vec![Bytes::from_static(b"\x01")]);
let s = format!("{ev}");
assert_eq!(s, r#"{"payload": [], "count": 1}"#);
}
#[test]
fn dyn_ack_display_with_attachments() {
let ack = DynAck::new("[]").with_attachments(vec![Bytes::from_static(b"\x01")]);
let s = format!("{ack}");
assert_eq!(s, r#"{"payload": [], "count": 1}"#);
}
#[test]
fn decode_binary_ack_missing_count_is_error() {
assert!(matches!(
decode(r#"6["ok"]"#),
Err(PacketError::MissingAttachmentCount)
));
}
#[test]
fn decode_binary_ack_missing_id_is_error() {
assert!(matches!(
decode(r#"61-["ok"]"#),
Err(PacketError::MissingAckId)
));
}
}