///|
/// The outcome of one physical delivery attempt.
pub(all) struct DeliveryResult {
ok : Bool
retryable : Bool
status_code : Int
error : String
duration_ms : Int
} derive(Eq, @debug.Debug)
///|
pub fn DeliveryResult::success(duration_ms : Int) -> DeliveryResult {
{ ok: true, retryable: false, status_code: 200, error: "", duration_ms, }
}
///|
/// A successful attempt that records the status code reported by the receiver,
/// so transports can distinguish `200` from `202` or `204`.
pub fn DeliveryResult::success_with_status(
status_code : Int,
duration_ms : Int,
) -> DeliveryResult {
{ ok: true, retryable: false, status_code, error: "", duration_ms, }
}
///|
pub fn DeliveryResult::failure(
status_code : Int,
error : String,
duration_ms : Int,
retryable : Bool,
) -> DeliveryResult {
{ ok: false, retryable, status_code, error, duration_ms, }
}
///|
/// A transport is a function that performs one delivery attempt.
/// HTTP adapters, test doubles, and in-process sinks all fit this shape.
pub type Transport = (Hook, WebhookEvent) -> DeliveryResult
///|
/// A scripted transport used in tests. It returns the configured results in
/// order and then reports retryable 500 errors once exhausted.
pub struct MockTransport {
results : Array[DeliveryResult]
next : @ref.Ref[Int]
} derive(@debug.Debug)
///|
pub fn MockTransport::new(results : Array[DeliveryResult]) -> MockTransport {
{ results, next: @ref.new(0), }
}
///|
pub fn MockTransport::send(
self : MockTransport,
_hook : Hook,
_event : WebhookEvent,
) -> DeliveryResult {
let index = self.next.val
self.next.val = index + 1
if index < self.results.length() {
self.results[index]
} else {
DeliveryResult::failure(500, "mock transport exhausted", 0, true)
}
}
///|
/// Convenience wrapper so a `MockTransport` can be passed directly to a
/// `DeliveryEngine` through a closure.
pub fn MockTransport::as_transport(self : MockTransport) -> Transport {
fn(hook, event) { self.send(hook, event) }
}