event_notification/adapter/
webhook.rs

1use crate::ChannelAdapter;
2use crate::Error;
3use crate::Event;
4use crate::WebhookConfig;
5use async_trait::async_trait;
6use reqwest::{Client, RequestBuilder};
7use std::time::Duration;
8use tokio::time::sleep;
9
10/// Webhook adapter for sending events to a webhook endpoint.
11pub struct WebhookAdapter {
12    config: WebhookConfig,
13    client: Client,
14}
15
16impl WebhookAdapter {
17    /// Creates a new Webhook adapter.
18    pub fn new(config: WebhookConfig) -> Self {
19        let client = Client::builder()
20            .timeout(Duration::from_secs(config.timeout))
21            .build()
22            .expect("Failed to build reqwest client");
23        Self { config, client }
24    }
25    /// Builds the request to send the event.
26    fn build_request(&self, event: &Event) -> RequestBuilder {
27        let mut request = self.client.post(&self.config.endpoint).json(event);
28        if let Some(token) = &self.config.auth_token {
29            request = request.header("Authorization", format!("Bearer {}", token));
30        }
31        if let Some(headers) = &self.config.custom_headers {
32            for (key, value) in headers {
33                request = request.header(key, value);
34            }
35        }
36        request
37    }
38}
39
40#[async_trait]
41impl ChannelAdapter for WebhookAdapter {
42    fn name(&self) -> String {
43        "webhook".to_string()
44    }
45
46    async fn send(&self, event: &Event) -> Result<(), Error> {
47        let mut attempt = 0;
48        loop {
49            match self.build_request(event).send().await {
50                Ok(response) => {
51                    response.error_for_status().map_err(Error::Http)?;
52                    return Ok(());
53                }
54                Err(e) if attempt < self.config.max_retries => {
55                    attempt += 1;
56                    tracing::warn!("Webhook attempt {} failed: {}. Retrying...", attempt, e);
57                    sleep(Duration::from_secs(2u64.pow(attempt))).await;
58                }
59                Err(e) => return Err(Error::Http(e)),
60            }
61        }
62    }
63}