#![allow(dead_code)]
use std::collections::HashMap;
use std::future::Future;
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
use std::time::Duration;
use reliar_core::{Classify, FailureKind, MessageId, Publisher, SerializedEnvelope};
#[derive(Debug)]
pub(crate) enum Never {}
impl std::fmt::Display for Never {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {}
}
}
impl std::error::Error for Never {}
impl Classify for Never {
fn kind(&self) -> FailureKind {
match *self {}
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum PublishStep {
Ok,
Stall(Duration),
Panic,
}
enum Script {
Always(PublishStep),
Keyed(HashMap<MessageId, PublishStep>),
}
impl Default for Script {
fn default() -> Self {
Self::Always(PublishStep::Ok)
}
}
#[derive(Default)]
struct Inner {
published: Vec<MessageId>,
in_flight: usize,
in_flight_peak: usize,
script: Script,
}
#[derive(Clone, Default)]
pub(crate) struct StubTransport {
inner: Arc<Mutex<Inner>>,
}
impl StubTransport {
pub(crate) fn ok() -> Self {
Self::default()
}
pub(crate) fn with_concurrency_probe(delay: Duration) -> Self {
Self::always(PublishStep::Stall(delay))
}
pub(crate) fn keyed(steps: impl IntoIterator<Item = (MessageId, PublishStep)>) -> Self {
Self {
inner: Arc::new(Mutex::new(Inner {
script: Script::Keyed(steps.into_iter().collect()),
..Inner::default()
})),
}
}
pub(crate) fn always(step: PublishStep) -> Self {
Self {
inner: Arc::new(Mutex::new(Inner {
script: Script::Always(step),
..Inner::default()
})),
}
}
fn lock(&self) -> MutexGuard<'_, Inner> {
self.inner.lock().unwrap_or_else(PoisonError::into_inner)
}
pub(crate) fn published(&self) -> Vec<MessageId> {
self.lock().published.clone()
}
pub(crate) fn count(&self, id: MessageId) -> usize {
self.lock()
.published
.iter()
.filter(|&&seen| seen == id)
.count()
}
pub(crate) fn in_flight_peak(&self) -> usize {
self.lock().in_flight_peak
}
fn step_for(&self, id: MessageId) -> PublishStep {
let mut guard = self.lock();
guard.published.push(id);
guard.in_flight += 1;
guard.in_flight_peak = guard.in_flight_peak.max(guard.in_flight);
match &guard.script {
Script::Always(step) => *step,
Script::Keyed(steps) => steps.get(&id).copied().unwrap_or(PublishStep::Ok),
}
}
}
struct InFlightGuard(StubTransport);
impl Drop for InFlightGuard {
fn drop(&mut self) {
self.0.lock().in_flight -= 1;
}
}
impl Publisher for StubTransport {
type Error = Never;
#[allow(
clippy::panic,
reason = "PublishStep::Panic exists to simulate a publish task crashing mid-flight — the \
panic is the stub's whole purpose here, not an accident"
)]
fn publish(
&self,
envelope: &SerializedEnvelope,
) -> impl Future<Output = Result<(), Self::Error>> + Send {
let stub = self.clone();
let id = envelope.id;
async move {
let step = stub.step_for(id);
let _in_flight = InFlightGuard(stub.clone());
match step {
PublishStep::Ok => {}
PublishStep::Stall(duration) => tokio::time::sleep(duration).await,
PublishStep::Panic => panic!("StubTransport: scripted publish panic"),
}
Ok(())
}
}
}