1use std::time::Duration;
6
7use serde_json::{Map, Value};
8
9use crate::errors::{AcpError, AcpResult};
10use crate::http_security::{HttpSecurityPolicy, build_http_client, validate_http_url};
11use crate::messages::AcpMessage;
12
13#[derive(Debug, Clone)]
14pub struct TransportResponse {
15 pub status_code: u16,
16 pub body: Option<Map<String, Value>>,
17 pub raw_body: String,
18}
19
20#[derive(Debug, Clone)]
21pub struct TransportClient {
22 client: reqwest::blocking::Client,
23 timeout_seconds: u64,
24 allow_insecure_http: bool,
25 mtls_enabled: bool,
26}
27
28impl TransportClient {
29 pub fn new(timeout_seconds: u64, policy: &HttpSecurityPolicy) -> AcpResult<Self> {
30 let client = build_http_client(timeout_seconds.max(1), policy)?;
31 Ok(Self {
32 client,
33 timeout_seconds: timeout_seconds.max(1),
34 allow_insecure_http: policy.allow_insecure_http,
35 mtls_enabled: policy.mtls_enabled,
36 })
37 }
38
39 pub fn post_json(&self, url: &str, body: &Map<String, Value>) -> AcpResult<TransportResponse> {
40 validate_http_url(
41 url,
42 self.allow_insecure_http,
43 self.mtls_enabled,
44 "HTTP transport request",
45 )?;
46 let response = self
47 .client
48 .post(url)
49 .header("Content-Type", "application/json")
50 .timeout(Duration::from_secs(self.timeout_seconds))
51 .json(body)
52 .send()?;
53 let status_code = response.status().as_u16();
54 let raw_body = response.text().unwrap_or_default();
55 let body = serde_json::from_str::<Value>(&raw_body)
56 .ok()
57 .and_then(|value| value.as_object().cloned());
58 Ok(TransportResponse {
59 status_code,
60 body,
61 raw_body,
62 })
63 }
64
65 pub fn send_to_relay(
66 &self,
67 relay_url: &str,
68 message: &AcpMessage,
69 ) -> AcpResult<Map<String, Value>> {
70 let relay_endpoint = if relay_url.ends_with('/') {
71 format!("{relay_url}messages")
72 } else {
73 format!("{relay_url}/messages")
74 };
75 let response = self.post_json(&relay_endpoint, &message.to_map()?)?;
76 if response.status_code != 200 {
77 return Err(AcpError::Transport(format!(
78 "Relay returned HTTP {} for message {}",
79 response.status_code, message.envelope.message_id
80 )));
81 }
82 response
83 .body
84 .ok_or_else(|| AcpError::Transport("Relay returned non-JSON response".to_string()))
85 }
86}