pub struct WebhookChannelManager {
pub retry_base: Duration,
pub max_retries: u32,
pub max_backoff: Duration,
pub circuit_threshold: u32,
/* private fields */
}Expand description
Webhook notification channel with built-in retry logic. The
manager keeps an internal map of subscriptions → target URL, and
deliver_all() POSTs the Activity Streams payload to each target.
Sprint 6 C additions (ADR-058):
- Optional RFC 9421 HTTP Message Signatures via
with_signer. Retry-Afterhonoured on 429.- 410 Gone treated as
FatalDrop; other 4xx retried as transient. - Full-jitter exponential back-off bounded by
max_backoff. - Simple per-manager circuit breaker — consecutive failures are
counted across
deliver_onecalls; once the threshold is reached further calls short-circuit toWebhookDelivery::TransientRetrywith acircuit openreason until a successful delivery resets the counter.
Fields§
§retry_base: DurationExponential backoff base (starting delay). Default 500ms.
max_retries: u32Max retry attempts on 5xx. Default 3 (preserved for backward
compat; tests that exercise Sprint 6 semantics call
with_max_attempts explicitly).
max_backoff: DurationCap on a single back-off wait. Default 1h.
circuit_threshold: u32Sprint 6 C: consecutive failures before the circuit opens.
Implementations§
Source§impl WebhookChannelManager
impl WebhookChannelManager
pub fn new() -> Self
Sourcepub fn with_client(client: Client) -> Self
pub fn with_client(client: Client) -> Self
Create a manager with a specific reqwest::Client (used in
tests with wiremock).
Sourcepub fn with_max_attempts(self, attempts: u32) -> Self
pub fn with_max_attempts(self, attempts: u32) -> Self
Override the max attempts (1 == no retries). Default 5.
Sourcepub fn with_max_backoff(self, max: Duration) -> Self
pub fn with_max_backoff(self, max: Duration) -> Self
Override the maximum single back-off wait. Default 1h.
Sourcepub fn with_circuit_threshold(self, threshold: u32) -> Self
pub fn with_circuit_threshold(self, threshold: u32) -> Self
Override the consecutive-failure threshold that opens the breaker. Default 10.
Sourcepub fn circuit_open(&self) -> bool
pub fn circuit_open(&self) -> bool
True iff the breaker is currently open.
Sourcepub fn consecutive_failures(&self) -> u32
pub fn consecutive_failures(&self) -> u32
Current consecutive-failure count. Public for observability and tests.
Sourcepub fn reset_circuit(&self)
pub fn reset_circuit(&self)
Reset the consecutive-failure counter (e.g. after operator intervention). Test hook, also exposed for admin UIs.
pub async fn subscribe(&self, topic: &str, target_url: &str) -> Subscription
pub async fn unsubscribe(&self, id: &str)
pub async fn active_subscriptions(&self) -> usize
Sourcepub async fn deliver_one(
&self,
url: &str,
note: &ChangeNotification,
) -> WebhookDelivery
pub async fn deliver_one( &self, url: &str, note: &ChangeNotification, ) -> WebhookDelivery
Deliver a single event to a single webhook URL, with full Sprint 6 C retry / back-off / circuit-breaker semantics.
Sourcepub async fn deliver_all(
&self,
note: &ChangeNotification,
topic_matches: impl Fn(&str) -> bool,
) -> Vec<(String, WebhookDelivery)>
pub async fn deliver_all( &self, note: &ChangeNotification, topic_matches: impl Fn(&str) -> bool, ) -> Vec<(String, WebhookDelivery)>
Deliver the notification to every matching subscription. Returns the per-subscription outcome.
Sourcepub async fn pump_from_storage(
self,
rx: Receiver<StorageEvent>,
pod_base: String,
)
pub async fn pump_from_storage( self, rx: Receiver<StorageEvent>, pod_base: String, )
Attach the manager to a storage event stream. Each event is translated into an Activity Streams notification and delivered to every subscription whose topic is a prefix of the event path.
Trait Implementations§
Source§impl Clone for WebhookChannelManager
impl Clone for WebhookChannelManager
Source§impl Default for WebhookChannelManager
impl Default for WebhookChannelManager
Source§impl Notifications for WebhookChannelManager
impl Notifications for WebhookChannelManager
Source§fn subscribe<'life0, 'async_trait>(
&'life0 self,
subscription: Subscription,
) -> Pin<Box<dyn Future<Output = Result<(), PodError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn subscribe<'life0, 'async_trait>(
&'life0 self,
subscription: Subscription,
) -> Pin<Box<dyn Future<Output = Result<(), PodError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Source§fn unsubscribe<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<(), PodError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn unsubscribe<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<(), PodError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Source§fn publish<'life0, 'life1, 'async_trait>(
&'life0 self,
topic: &'life1 str,
notification: ChangeNotification,
) -> Pin<Box<dyn Future<Output = Result<(), PodError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn publish<'life0, 'life1, 'async_trait>(
&'life0 self,
topic: &'life1 str,
notification: ChangeNotification,
) -> Pin<Box<dyn Future<Output = Result<(), PodError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
topic.