#![allow(dead_code)]
use std::collections::HashMap;
use serde::Serialize;
use super::*;
use crate::{
FixedName, MessageHeaders, NoHeaders, OutgoingDestination, OutgoingMessage, WithHeaders,
};
#[derive(Debug)]
struct A;
impl OutSlot for A {
const NAME: &'static str = "A";
type Destination = Reads;
}
#[derive(Debug)]
struct B;
impl OutSlot for B {
const NAME: &'static str = "B";
type Destination = Reads;
}
#[test]
fn binds_slots_in_any_order() {
let init = <(A, B) as InitSlots>::init();
let step = BindSlot::<B, &str, SlotPos<1>>::bind(init, "b");
let done = BindSlot::<A, &str, SlotPos<0>>::bind(step, "a");
let (a, b) = done;
assert_eq!(a.into_source(), "a");
assert_eq!(b.into_source(), "b");
}
#[derive(Debug)]
struct Events;
impl OutSlot for Events {
const NAME: &'static str = "Events";
type Destination = Reads;
fn outgoing() -> Vec<OutgoingMessageMetadata> {
vec![OutgoingMessageMetadata::new("events.progress", "Progress")]
}
}
#[derive(Serialize)]
struct Progress {
samples: HashMap<(u8, u8), u8>,
}
impl Progress {
fn new() -> Self {
Self {
samples: HashMap::from([((1, 2), 3)]),
}
}
}
impl MessageHeaders for Progress {
type Contract = NoHeaders;
}
impl OutgoingDestination for Progress {
type Form = FixedName;
const DESTINATION: &'static str = "events.progress";
}
impl PublishedThrough<Events> for Progress {}
#[derive(Serialize)]
struct Meta {
task_id: u64,
}
#[derive(Serialize)]
struct NestedMeta {
inner: Meta,
}
#[derive(Serialize)]
struct Done {
key: &'static str,
}
impl MessageHeaders for Done {
type Contract = WithHeaders<NestedMeta>;
}
impl OutgoingDestination for Done {
type Form = FixedName;
const DESTINATION: &'static str = "events.done";
}
impl PublishedThrough<Events> for Done {}
#[test]
fn an_unrestricted_declaration_documents_the_whole_dictionary() {
let declared = <() as OutMessages<Events>>::outgoing();
assert_eq!(declared.len(), 1);
assert_eq!(declared[0].channel, "events.progress");
assert_eq!(declared[0].message_type, "Progress");
}
#[cfg(feature = "memory")]
#[tokio::test]
async fn a_slot_publisher_delegates_the_transaction_protocol() {
use futures::StreamExt;
use crate::Subscriber;
use crate::memory::MemoryBroker;
let broker = MemoryBroker::new();
let mut subscriber = broker.subscribe("slots.ledger");
let slot = SlotPublisher::<_, Events>::new(broker.publisher());
slot.begin_transaction().await.expect("begin failed");
slot.publish(OutgoingMessage::new("slots.ledger", b"staged"), None)
.await
.expect("publish failed");
slot.abort().await.expect("abort failed");
let mut stream = std::pin::pin!(subscriber.stream());
assert!(
futures::poll!(stream.next()).is_pending(),
"an aborted transaction discards what it staged",
);
}
#[cfg(all(feature = "memory", feature = "json"))]
#[tokio::test]
async fn the_builder_separates_the_encode_and_the_headers_failure() {
use futures::StreamExt;
use crate::Subscriber;
use crate::codec::JsonCodec;
use crate::memory::MemoryBroker;
use crate::runtime::{PublishError, PublishIdentity, Slot};
let broker = MemoryBroker::new();
let mut subscriber = broker.subscribe("events.done");
let slot = Slot::<Events, _, _>::wired(broker.publisher(), JsonCodec, PublishIdentity);
let encode = slot
.message(&Progress::new())
.publish()
.await
.expect_err("the codec cannot encode this payload");
assert!(
matches!(encode, PublishError::Encode(_)),
"the encode arm must be distinguishable from a broker rejection: {encode:?}",
);
let headers = NestedMeta {
inner: Meta { task_id: 7 },
};
let rejected = slot
.message(&Done { key: "out/1" })
.with_headers(&headers)
.publish()
.await
.expect_err("a nested struct is not a header value");
assert!(
matches!(rejected, PublishError::Headers(_)),
"the headers arm names what failed: {rejected:?}",
);
assert!(
rejected
.to_string()
.contains("serializing the typed headers"),
"the message must point at the headers: {rejected}",
);
let mut stream = std::pin::pin!(subscriber.stream());
assert!(
futures::poll!(stream.next()).is_pending(),
"nothing may be published once a position fails",
);
}