1use crate::error::{Error, Result};
4use reqwest::{Client as ReqwestClient, StatusCode};
5use std::time::Duration;
6use tokio::time::timeout;
7use tracing::{debug, error, info};
8
9const DEFAULT_TIMEOUT_SECS: u64 = 30;
11
12pub struct DIDCommClient {
14 client: ReqwestClient,
16
17 timeout_secs: u64,
19}
20
21impl DIDCommClient {
22 pub fn new(timeout_secs: Option<u64>) -> Self {
24 Self {
25 client: ReqwestClient::new(),
26 timeout_secs: timeout_secs.unwrap_or(DEFAULT_TIMEOUT_SECS),
27 }
28 }
29
30 pub fn with_timeout(mut self, timeout_secs: u64) -> Self {
32 self.timeout_secs = timeout_secs;
33 self
34 }
35
36 pub async fn deliver_message(&self, endpoint: &str, message: &str) -> Result<()> {
38 info!("Delivering DIDComm message to {}", endpoint);
39 debug!("Message size: {} bytes", message.len());
40
41 let request_timeout = Duration::from_secs(self.timeout_secs);
43
44 let request = self
46 .client
47 .post(endpoint)
48 .header("Content-Type", "application/didcomm-encrypted+json")
49 .body(message.to_string());
50
51 let response = match timeout(request_timeout, request.send()).await {
53 Ok(result) => match result {
54 Ok(response) => response,
55 Err(e) => return Err(Error::Http(format!("Failed to send message: {}", e))),
56 },
57 Err(_) => {
58 return Err(Error::Http(format!(
59 "Request timed out after {} seconds",
60 self.timeout_secs
61 )))
62 }
63 };
64
65 match response.status() {
67 StatusCode::OK | StatusCode::ACCEPTED | StatusCode::CREATED => {
68 info!("Message delivered successfully");
69 Ok(())
70 }
71 status => {
72 let error_body = match response.text().await {
74 Ok(body) => body,
75 Err(_) => "<unable to read error response>".to_string(),
76 };
77
78 error!(
79 "Failed to deliver message: Status {}, Body: {}",
80 status, error_body
81 );
82 Err(Error::Http(format!(
83 "Delivery failed with status code {}: {}",
84 status, error_body
85 )))
86 }
87 }
88 }
89}
90
91impl Default for DIDCommClient {
92 fn default() -> Self {
93 Self::new(None)
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[tokio::test]
103 async fn test_client_creation() {
104 let client = DIDCommClient::new(Some(10));
106 assert_eq!(client.timeout_secs, 10);
107
108 let default_client = DIDCommClient::default();
110 assert_eq!(default_client.timeout_secs, DEFAULT_TIMEOUT_SECS);
111
112 let custom_client = DIDCommClient::default().with_timeout(15);
114 assert_eq!(custom_client.timeout_secs, 15);
115 }
116}