use std::time::Duration;
use serde_json::Value;
use trust_tasks_rs::TrustTask;
use crate::error::TrqlError;
use crate::transport::{TransportKind, TrqlTransport};
#[derive(Debug, Clone)]
pub struct HttpsTransportConfig {
pub base_url: String,
pub timeout: Duration,
pub connect_timeout: Duration,
pub bearer_token: Option<String>,
}
impl HttpsTransportConfig {
pub fn new(base_url: impl Into<String>) -> Self {
Self {
base_url: base_url.into(),
timeout: Duration::from_secs(30),
connect_timeout: Duration::from_secs(10),
bearer_token: None,
}
}
}
pub struct HttpsTransport {
http: reqwest::Client,
endpoint: reqwest::Url,
bearer_token: Option<String>,
timeout: Duration,
}
impl HttpsTransport {
pub fn new(config: HttpsTransportConfig) -> Result<Self, TrqlError> {
let endpoint: reqwest::Url =
format!("{}/trust-tasks", config.base_url.trim_end_matches('/'))
.parse()
.map_err(|e| TrqlError::Config(format!("invalid registry base URL: {e}")))?;
let http = reqwest::Client::builder()
.timeout(config.timeout)
.connect_timeout(config.connect_timeout)
.build()
.map_err(|e| TrqlError::Config(format!("could not build HTTP client: {e}")))?;
Ok(Self {
http,
endpoint,
bearer_token: config.bearer_token,
timeout: config.timeout,
})
}
}
#[async_trait::async_trait]
impl TrqlTransport for HttpsTransport {
fn kind(&self) -> TransportKind {
TransportKind::Https
}
async fn exchange(&self, request: TrustTask<Value>) -> Result<TrustTask<Value>, TrqlError> {
let mut http_request = self.http.post(self.endpoint.clone()).json(&request);
if let Some(token) = &self.bearer_token {
http_request = http_request.bearer_auth(token);
}
let response = http_request.send().await.map_err(|e| {
if e.is_timeout() {
TrqlError::Timeout {
kind: TransportKind::Https,
waited_secs: self.timeout.as_secs(),
}
} else if e.is_connect() {
TrqlError::Transport {
kind: TransportKind::Https,
detail: format!("could not connect to {}: {e}", self.endpoint),
}
} else {
TrqlError::Transport {
kind: TransportKind::Https,
detail: e.to_string(),
}
}
})?;
let status = response.status();
let body = response.bytes().await.map_err(|e| TrqlError::Transport {
kind: TransportKind::Https,
detail: format!("failed reading response body: {e}"),
})?;
match serde_json::from_slice::<TrustTask<Value>>(&body) {
Ok(document) => Ok(document),
Err(e) if status.is_success() => Err(TrqlError::Contract(format!(
"HTTP {status} body is not a Trust Task document: {e}"
))),
Err(_) => Err(TrqlError::Transport {
kind: TransportKind::Https,
detail: format!(
"HTTP {status} with non-Trust-Task body: {}",
String::from_utf8_lossy(&body)
),
}),
}
}
}