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