///|
pub(all) enum DeliveryState {
DeliverySucceeded
DeliveryDeadLettered
DeliveryDropped(String)
} derive(Eq, Debug)
///|
pub(all) struct DeliveryAttempt {
number : Int
event : Event
report : DispatchReport
} derive(Eq, Debug)
///|
pub(all) struct DeliveryRun {
original : Event
attempts : Array[DeliveryAttempt]
state : DeliveryState
dead_letter : Event?
dead_letter_report : DispatchReport?
} derive(Eq, Debug)
///|
pub(all) struct DeliveryBatch {
runs : Array[DeliveryRun]
succeeded : Int
dead_lettered : Int
dropped : Int
total_attempts : Int
} derive(Eq, Debug)
///|
fn failure_reason(report : DispatchReport) -> String {
let failures = collect_failures(report)
match failures.get(0) {
Some(reason) => reason
None => "dispatch failed"
}
}
///|
fn successful_run(
original : Event,
attempts : Array[DeliveryAttempt],
) -> DeliveryRun {
{
original,
attempts,
state: DeliverySucceeded,
dead_letter: None,
dead_letter_report: None,
}
}
///|
fn dropped_run(
original : Event,
attempts : Array[DeliveryAttempt],
reason : String,
) -> DeliveryRun {
{
original,
attempts,
state: DeliveryDropped(reason),
dead_letter: None,
dead_letter_report: None,
}
}
///|
fn dead_lettered_run(
original : Event,
attempts : Array[DeliveryAttempt],
dead_letter : Event,
report : DispatchReport,
) -> DeliveryRun {
{
original,
attempts,
state: DeliveryDeadLettered,
dead_letter: Some(dead_letter),
dead_letter_report: Some(report),
}
}
///|
/// Publishes an event and executes RetryPolicy decisions until the event
/// succeeds, is converted to a dead-letter event, or is dropped.
pub fn EventBus::publish_with_retry(
self : EventBus,
event : Event,
policy : RetryPolicy,
) -> (EventBus, DeliveryRun) {
let attempts : Array[DeliveryAttempt] = []
let mut bus = self
let mut current = event
let mut attempt = 1
while true {
let (next, report) = bus.publish(current)
bus = next
attempts.push({ number: attempt, event: current, report })
if report.is_ok() {
return (bus, successful_run(event, attempts))
}
let reason = failure_reason(report)
match policy.decide(current, attempt, reason) {
Retry(retry_event) => {
current = retry_event
attempt = attempt + 1
}
DeadLetter(dead_event) => {
let (after_dead, dead_report) = bus.publish(dead_event)
return (
after_dead,
dead_lettered_run(event, attempts, dead_event, dead_report),
)
}
Drop(drop_reason) =>
return (bus, dropped_run(event, attempts, drop_reason))
}
}
(bus, dropped_run(event, attempts, "unreachable"))
}
///|
pub fn DeliveryRun::attempt_count(self : DeliveryRun) -> Int {
self.attempts.length()
}
///|
pub fn DeliveryRun::succeeded(self : DeliveryRun) -> Bool {
self.state == DeliverySucceeded
}
///|
pub fn DeliveryRun::was_retried(self : DeliveryRun) -> Bool {
self.attempts.length() > 1
}
///|
pub fn DeliveryRun::last_report(self : DeliveryRun) -> DispatchReport? {
if self.attempts.length() == 0 {
None
} else {
match self.attempts.get(self.attempts.length() - 1) {
Some(attempt) => Some(attempt.report)
None => None
}
}
}
///|
pub fn DeliveryRun::failure_reasons(self : DeliveryRun) -> Array[String] {
let reasons : Array[String] = []
for attempt in self.attempts {
for reason in collect_failures(attempt.report) {
reasons.push(reason)
}
}
reasons
}
///|
fn summarize_delivery_runs(runs : Array[DeliveryRun]) -> DeliveryBatch {
let mut succeeded = 0
let mut dead_lettered = 0
let mut dropped = 0
let mut total_attempts = 0
for run in runs {
total_attempts = total_attempts + run.attempt_count()
match run.state {
DeliverySucceeded => succeeded = succeeded + 1
DeliveryDeadLettered => dead_lettered = dead_lettered + 1
DeliveryDropped(_) => dropped = dropped + 1
}
}
{ runs, succeeded, dead_lettered, dropped, total_attempts }
}
///|
pub fn EventBus::publish_many_with_retry(
self : EventBus,
events : Array[Event],
policy : RetryPolicy,
) -> (EventBus, DeliveryBatch) {
let runs : Array[DeliveryRun] = []
let mut bus = self
for event in events {
let (next, run) = bus.publish_with_retry(event, policy)
bus = next
runs.push(run)
}
(bus, summarize_delivery_runs(runs))
}
///|
pub fn DeliveryBatch::is_ok(self : DeliveryBatch) -> Bool {
self.dead_lettered == 0 && self.dropped == 0
}
///|
pub fn DeliveryBatch::failed_runs(self : DeliveryBatch) -> Array[DeliveryRun] {
let failed : Array[DeliveryRun] = []
for run in self.runs {
if !run.succeeded() {
failed.push(run)
}
}
failed
}
///|
pub fn DeliveryBatch::reports(self : DeliveryBatch) -> Array[DispatchReport] {
let reports : Array[DispatchReport] = []
for run in self.runs {
for attempt in run.attempts {
reports.push(attempt.report)
}
match run.dead_letter_report {
Some(report) => reports.push(report)
None => ()
}
}
reports
}
///|
pub fn DeliveryBatch::metrics(self : DeliveryBatch) -> BusMetrics {
metrics_from_reports(self.reports())
}