Skip to main content

systemprompt_agent/services/external_integrations/webhook/service/
delivery.rs

1use super::WebhookService;
2use super::types::{WebhookConfig, WebhookDeliveryResult, WebhookStats, WebhookTestResult};
3use crate::models::external_integrations::{IntegrationError, IntegrationResult};
4use serde_json::Value;
5use std::collections::HashMap;
6
7impl WebhookService {
8    pub async fn send_webhook(
9        &self,
10        url: &str,
11        payload: Value,
12        config: Option<WebhookConfig>,
13    ) -> IntegrationResult<WebhookDeliveryResult> {
14        let config = config.unwrap_or_else(WebhookConfig::default);
15
16        let mut request_builder = self
17            .http_client
18            .post(url)
19            .json(&payload)
20            .header("Content-Type", "application/json")
21            .header(
22                "User-Agent",
23                concat!("systemprompt.io-Webhook/", env!("CARGO_PKG_VERSION")),
24            );
25
26        for (key, value) in &config.headers {
27            request_builder = request_builder.header(key, value);
28        }
29
30        if let Some(secret) = &config.secret {
31            let signature = Self::generate_signature(secret, &payload)?;
32            request_builder = request_builder.header("X-Webhook-Signature", signature);
33        }
34
35        if let Some(timeout) = config.timeout {
36            request_builder = request_builder.timeout(timeout);
37        }
38
39        let start_time = std::time::Instant::now();
40
41        match request_builder.send().await {
42            Ok(response) => {
43                let status = response.status().as_u16();
44                let headers: HashMap<String, String> = response
45                    .headers()
46                    .iter()
47                    .map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string()))
48                    .collect();
49
50                let body = response
51                    .text()
52                    .await
53                    .unwrap_or_else(|e| format!("<error reading response: {}>", e));
54                let duration = start_time.elapsed();
55
56                Ok(WebhookDeliveryResult {
57                    success: (200..300).contains(&status),
58                    status_code: status,
59                    response_body: body,
60                    response_headers: headers,
61                    duration_ms: duration.as_millis() as u64,
62                    error: None,
63                })
64            },
65            Err(e) => {
66                let duration = start_time.elapsed();
67                Ok(WebhookDeliveryResult {
68                    success: false,
69                    status_code: 0,
70                    response_body: String::new(),
71                    response_headers: HashMap::new(),
72                    duration_ms: duration.as_millis() as u64,
73                    error: Some(e.to_string()),
74                })
75            },
76        }
77    }
78
79    pub async fn get_endpoint_stats(&self, endpoint_id: &str) -> IntegrationResult<WebhookStats> {
80        let endpoint = {
81            let endpoints = self.endpoints.read().await;
82            endpoints.get(endpoint_id).cloned().ok_or_else(|| {
83                IntegrationError::Webhook(format!("Endpoint not found: {endpoint_id}"))
84            })?
85        };
86
87        Ok(WebhookStats {
88            endpoint_id: endpoint.id,
89            total_requests: 0,
90            successful_requests: 0,
91            failed_requests: 0,
92            last_request_at: None,
93            average_response_time_ms: 0,
94        })
95    }
96
97    pub async fn test_endpoint(&self, endpoint_id: &str) -> IntegrationResult<WebhookTestResult> {
98        let endpoint = {
99            let endpoints = self.endpoints.read().await;
100            endpoints.get(endpoint_id).cloned().ok_or_else(|| {
101                IntegrationError::Webhook(format!("Endpoint not found: {endpoint_id}"))
102            })?
103        };
104
105        let test_payload = serde_json::json!({
106            "test": true,
107            "timestamp": chrono::Utc::now().to_rfc3339(),
108            "endpoint_id": endpoint_id
109        });
110
111        let config = WebhookConfig {
112            secret: endpoint.secret.clone(),
113            headers: endpoint.headers.clone(),
114            timeout: Some(std::time::Duration::from_secs(10)),
115        };
116
117        let result = self
118            .send_webhook(&endpoint.url, test_payload, Some(config))
119            .await?;
120
121        Ok(WebhookTestResult {
122            endpoint_id: endpoint.id,
123            success: result.success,
124            status_code: result.status_code,
125            response_time_ms: result.duration_ms,
126            error: result.error,
127        })
128    }
129}