1use std::time::Duration;
13
14use serde_json::Value;
15use trust_tasks_rs::TrustTask;
16
17use crate::error::TrqlError;
18use crate::transport::{TransportKind, TrqlTransport};
19
20#[derive(Debug, Clone)]
22pub struct HttpsTransportConfig {
23 pub base_url: String,
26 pub timeout: Duration,
28 pub connect_timeout: Duration,
30 pub bearer_token: Option<String>,
32}
33
34impl HttpsTransportConfig {
35 pub fn new(base_url: impl Into<String>) -> Self {
37 Self {
38 base_url: base_url.into(),
39 timeout: Duration::from_secs(30),
40 connect_timeout: Duration::from_secs(10),
41 bearer_token: None,
42 }
43 }
44}
45
46pub struct HttpsTransport {
51 http: reqwest::Client,
52 endpoint: reqwest::Url,
53 bearer_token: Option<String>,
54 timeout: Duration,
55}
56
57impl HttpsTransport {
58 pub fn new(config: HttpsTransportConfig) -> Result<Self, TrqlError> {
61 let endpoint: reqwest::Url =
62 format!("{}/trust-tasks", config.base_url.trim_end_matches('/'))
63 .parse()
64 .map_err(|e| TrqlError::Config(format!("invalid registry base URL: {e}")))?;
65 let http = reqwest::Client::builder()
66 .timeout(config.timeout)
67 .connect_timeout(config.connect_timeout)
68 .build()
69 .map_err(|e| TrqlError::Config(format!("could not build HTTP client: {e}")))?;
70 Ok(Self {
71 http,
72 endpoint,
73 bearer_token: config.bearer_token,
74 timeout: config.timeout,
75 })
76 }
77}
78
79#[async_trait::async_trait]
80impl TrqlTransport for HttpsTransport {
81 fn kind(&self) -> TransportKind {
82 TransportKind::Https
83 }
84
85 async fn exchange(&self, request: TrustTask<Value>) -> Result<TrustTask<Value>, TrqlError> {
86 let mut http_request = self.http.post(self.endpoint.clone()).json(&request);
87 if let Some(token) = &self.bearer_token {
88 http_request = http_request.bearer_auth(token);
89 }
90
91 let response = http_request.send().await.map_err(|e| {
92 if e.is_timeout() {
93 TrqlError::Timeout {
94 kind: TransportKind::Https,
95 waited_secs: self.timeout.as_secs(),
96 }
97 } else if e.is_connect() {
98 TrqlError::Transport {
99 kind: TransportKind::Https,
100 detail: format!("could not connect to {}: {e}", self.endpoint),
101 }
102 } else {
103 TrqlError::Transport {
104 kind: TransportKind::Https,
105 detail: e.to_string(),
106 }
107 }
108 })?;
109
110 let status = response.status();
111 let body = response.bytes().await.map_err(|e| TrqlError::Transport {
112 kind: TransportKind::Https,
113 detail: format!("failed reading response body: {e}"),
114 })?;
115
116 match serde_json::from_slice::<TrustTask<Value>>(&body) {
119 Ok(document) => Ok(document),
120 Err(e) if status.is_success() => Err(TrqlError::Contract(format!(
121 "HTTP {status} body is not a Trust Task document: {e}"
122 ))),
123 Err(_) => Err(TrqlError::Transport {
124 kind: TransportKind::Https,
125 detail: format!(
126 "HTTP {status} with non-Trust-Task body: {}",
127 String::from_utf8_lossy(&body)
128 ),
129 }),
130 }
131 }
132}