#![cfg(all(
feature = "memory",
feature = "macros",
feature = "json",
feature = "testing"
))]
use std::fmt;
use ruststream::memory::prelude::*;
use ruststream::testing::TestApp;
#[derive(Outgoing, Serialized)]
struct Export(Vec<u8>);
#[derive(Debug)]
struct SequenceTooHigh(u32);
impl fmt::Display for SequenceTooHigh {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "sequence {} does not fit the frame", self.0)
}
}
impl std::error::Error for SequenceTooHigh {}
#[derive(Debug)]
struct BadFrame;
impl fmt::Display for BadFrame {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("a tick frame is four big-endian bytes")
}
}
impl std::error::Error for BadFrame {}
const MAX_SEQUENCE: u32 = 0x00ff_ffff;
#[derive(Debug, PartialEq, Outgoing, Serialized, Deserialized)]
#[outgoing(name = "wire.ticks")]
#[wire(encode = write_tick, decode = read_tick)]
struct Tick {
seq: u32,
}
fn write_tick(tick: &Tick, buf: &mut BytesMut) -> Result<(), SequenceTooHigh> {
if tick.seq > MAX_SEQUENCE {
return Err(SequenceTooHigh(tick.seq));
}
buf.extend_from_slice(&tick.seq.to_be_bytes());
Ok(())
}
fn read_tick(payload: &[u8]) -> Result<Tick, BadFrame> {
let bytes: [u8; 4] = payload.try_into().map_err(|_| BadFrame)?;
Ok(Tick {
seq: u32::from_be_bytes(bytes),
})
}
#[derive(Debug, Outgoing, Serialized)]
#[outgoing(name = "wire.beats")]
#[wire(encode = write_beat)]
struct Beat {
at: u8,
}
fn write_beat(beat: &Beat, buf: &mut BytesMut) {
buf.extend_from_slice(&[beat.at]);
}
#[subscriber("wire.ticks")]
async fn count(tick: &Tick) -> HandlerOutcome {
if tick.seq == 9 {
HandlerOutcome::ack()
} else {
HandlerOutcome::drop()
}
}
#[test]
fn a_value_that_holds_its_bytes_lends_them() {
let export = Export(vec![1, 2, 3]);
let mut buf = BytesMut::new();
let answer = export.wire_bytes(&mut buf).expect("held bytes cannot fail");
let WireBytes::Own(lent) = answer else {
panic!("a value that holds its bytes lends them");
};
assert!(std::ptr::eq(lent.as_ptr(), export.0.as_ptr()));
assert_eq!(buf, "");
}
#[test]
fn a_value_that_holds_fields_writes_them_into_the_buffer() {
let tick = Tick { seq: 9 };
let mut buf = BytesMut::new();
let answer = tick.wire_bytes(&mut buf).expect("in range");
assert!(matches!(answer, WireBytes::InBuffer));
assert_eq!(&buf[..], &[0, 0, 0, 9]);
}
#[test]
fn an_infallible_writer_needs_no_result() {
let beat = Beat { at: 4 };
let mut buf = BytesMut::new();
assert!(matches!(
beat.wire_bytes(&mut buf).expect("cannot fail"),
WireBytes::InBuffer
));
assert_eq!(&buf[..], &[4]);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_self_serialising_message_is_asserted_on_its_own_bytes() {
let app =
RustStream::new(AppInfo::new("wire", "0.1.0")).with_broker(MemoryBroker::new(), |b| {
b.include(count);
});
let tb = TestApp::start(app).await.expect("harness start");
tb.message(&Tick { seq: 9 })
.publish()
.await
.expect("inject");
let broker = tb.broker::<MemoryBroker>();
let ticks = broker.subscriber("wire.ticks");
let received = ticks.received_raw();
let decoded = Tick::from_payload(&received[0]).expect("a tick frame");
assert_eq!(decoded, Tick { seq: 9 });
ticks
.assert_called_once()
.with_raw(&[0, 0, 0, 9])
.settled(HandlerOutcome::ack());
tb.shutdown().await.expect("graceful shutdown");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_rejected_frame_is_settled_by_the_decode_policy() {
let app =
RustStream::new(AppInfo::new("wire", "0.1.0")).with_broker(MemoryBroker::new(), |b| {
b.include(count);
});
let tb = TestApp::start(app).await.expect("harness start");
tb.message(&Export(vec![1]))
.to("wire.ticks")
.publish()
.await
.expect("inject");
tb.broker::<MemoryBroker>()
.subscriber("wire.ticks")
.assert_called_once()
.settled(HandlerOutcome::drop());
tb.shutdown().await.expect("graceful shutdown");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_rejected_value_reports_its_own_encoder() {
let app =
RustStream::new(AppInfo::new("wire", "0.1.0")).with_broker(MemoryBroker::new(), |b| {
b.include(count);
});
let tb = TestApp::start(app).await.expect("harness start");
let err = tb
.message(&Tick {
seq: MAX_SEQUENCE + 1,
})
.publish()
.await
.expect_err("the writer rejects this sequence");
let reported = err.to_string();
assert!(
reported.contains("serializing the message's own bytes failed"),
"unexpected error: {reported}"
);
assert!(
reported.contains("does not fit the frame"),
"the format's own error is lost: {reported}"
);
tb.shutdown().await.expect("graceful shutdown");
}