use std::convert::Infallible;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use ruststream::memory::MemoryBroker;
use ruststream::runtime::{AppInfo, Context, FromContext, HandlerResult, RustStream, State};
use ruststream::testing::TestApp;
use ruststream::{FromRef, subscriber};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Order {
id: u64,
}
struct AppState {
hits: Arc<AtomicU32>,
}
struct Hits(Arc<AtomicU32>);
impl<C: Send> FromContext<C, AppState> for Hits {
type Rejection = HandlerResult;
async fn from_context(ctx: &mut Context<'_, C, AppState>) -> Result<Self, HandlerResult> {
Ok(Self(ctx.state().hits.clone()))
}
}
#[subscriber("orders")]
async fn record(_order: &Order, hits: Hits) -> HandlerResult {
hits.0.fetch_add(1, Ordering::Relaxed);
HandlerResult::Ack
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn extractor_resolves_from_state() {
let hits = Arc::new(AtomicU32::new(0));
let state_hits = hits.clone();
let app = RustStream::new(AppInfo::new("orders", "0.1.0"))
.on_startup(move |()| async move { Ok::<_, Infallible>(AppState { hits: state_hits }) })
.with_broker(MemoryBroker::new(), |b| b.include(record));
let tb = TestApp::start(app).await.expect("start");
tb.broker::<MemoryBroker>()
.publish("orders", &Order { id: 7 })
.await
.expect("publish");
tb.broker::<MemoryBroker>()
.subscriber("orders")
.assert_called_once()
.with(&Order { id: 7 })
.settled(HandlerResult::Ack);
assert_eq!(
hits.load(Ordering::Relaxed),
1,
"extractor handed the counter to the handler"
);
}
struct GuardState {
ran: Arc<AtomicBool>,
}
struct Deny;
impl<C: Send> FromContext<C, GuardState> for Deny {
type Rejection = HandlerResult;
async fn from_context(_ctx: &mut Context<'_, C, GuardState>) -> Result<Self, HandlerResult> {
Err(HandlerResult::drop())
}
}
#[subscriber("guard")]
async fn guarded(
_order: &Order,
ctx: &mut Context<'_, (), GuardState>,
_deny: Deny,
) -> HandlerResult {
ctx.state().ran.store(true, Ordering::Relaxed);
HandlerResult::Ack
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn extractor_rejection_short_circuits() {
let ran = Arc::new(AtomicBool::new(false));
let state_ran = ran.clone();
let app = RustStream::new(AppInfo::new("guard", "0.1.0"))
.on_startup(move |()| async move { Ok::<_, Infallible>(GuardState { ran: state_ran }) })
.with_broker(MemoryBroker::new(), |b| b.include(guarded));
let tb = TestApp::start(app).await.expect("start");
tb.broker::<MemoryBroker>()
.publish("guard", &Order { id: 1 })
.await
.expect("publish");
tb.broker::<MemoryBroker>()
.subscriber("guard")
.assert_called_once()
.with(&Order { id: 1 })
.settled(HandlerResult::drop());
assert!(
!ran.load(Ordering::Relaxed),
"the handler body must not run when an extractor rejects"
);
}
#[derive(Clone)]
struct Tally(Arc<AtomicU32>);
#[derive(FromRef)]
struct DerivedState {
tally: Tally,
#[from_ref(skip)]
_label: &'static str,
}
#[subscriber("derived")]
async fn derived(_order: &Order, State(tally): State<Tally>) -> HandlerResult {
tally.0.fetch_add(1, Ordering::Relaxed);
HandlerResult::Ack
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn derive_from_ref_injects_fields() {
let count = Arc::new(AtomicU32::new(0));
let tally = Tally(count.clone());
let app = RustStream::new(AppInfo::new("derived", "0.1.0"))
.on_startup(move |()| async move {
Ok::<_, Infallible>(DerivedState {
tally,
_label: "svc",
})
})
.with_broker(MemoryBroker::new(), |b| b.include(derived));
let tb = TestApp::start(app).await.expect("start");
tb.broker::<MemoryBroker>()
.publish("derived", &Order { id: 3 })
.await
.expect("publish");
tb.broker::<MemoryBroker>()
.subscriber("derived")
.assert_called_once()
.with(&Order { id: 3 })
.settled(HandlerResult::Ack);
assert_eq!(
count.load(Ordering::Relaxed),
1,
"derived extractor handed the field to the handler"
);
}