Skip to main content

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

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