systemprompt_agent/services/external_integrations/webhook/service/
delivery.rs1use 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}