use std::time::Duration;
use crate::error::{AriError, Result};
use crate::event::AriMessage;
use crate::websocket::WsEventListener;
use crate::ws_transport::WsTransport;
use asterisk_rs_core::auth::Credentials;
use asterisk_rs_core::config::ReconnectPolicy;
use asterisk_rs_core::event::EventBus;
pub(crate) struct TransportResponse {
pub status: u16,
pub body: Option<String>,
}
pub(crate) enum TransportInner {
Http(HttpTransport),
WebSocket(WsTransport),
}
impl TransportInner {
pub async fn request(
&self,
method: &str,
path: &str,
body: Option<String>,
) -> Result<TransportResponse> {
match self {
Self::Http(t) => t.request(method, path, body).await,
Self::WebSocket(t) => t.request(method, path, body).await,
}
}
pub fn shutdown(&self) {
match self {
Self::Http(t) => t.ws_listener.shutdown(),
Self::WebSocket(t) => t.shutdown(),
}
}
}
pub(crate) struct HttpTransport {
client: reqwest::Client,
base_url: String,
credentials: Credentials,
ws_listener: WsEventListener,
}
impl HttpTransport {
pub fn new(
base_url: &str,
credentials: Credentials,
ws_url: String,
event_bus: EventBus<AriMessage>,
reconnect: ReconnectPolicy,
) -> Result<Self> {
let client = reqwest::Client::builder()
.connect_timeout(Duration::from_secs(10))
.timeout(Duration::from_secs(30))
.build()
.map_err(AriError::Http)?;
let ws_listener = WsEventListener::spawn(ws_url, event_bus, reconnect);
Ok(Self {
client,
base_url: base_url.trim_end_matches('/').to_owned(),
credentials,
ws_listener,
})
}
pub async fn request(
&self,
method: &str,
path: &str,
body: Option<String>,
) -> Result<TransportResponse> {
let url = format!("{}/{}", self.base_url, path.trim_start_matches('/'));
let http_method = parse_method(method)?;
let mut req = self
.client
.request(http_method, &url)
.basic_auth(self.credentials.username(), Some(self.credentials.secret()));
if let Some(json_body) = body {
req = req
.header(reqwest::header::CONTENT_TYPE, "application/json")
.body(json_body);
}
let response = req.send().await.map_err(AriError::Http)?;
let status = response.status().as_u16();
if response.status().is_client_error() || response.status().is_server_error() {
let message = response
.text()
.await
.unwrap_or_else(|_| "failed to read error body".to_owned());
return Err(AriError::Api { status, message });
}
let text = response.text().await.map_err(AriError::Http)?;
let body = if text.is_empty() { None } else { Some(text) };
Ok(TransportResponse { status, body })
}
}
fn parse_method(method: &str) -> Result<reqwest::Method> {
match method {
"GET" => Ok(reqwest::Method::GET),
"POST" => Ok(reqwest::Method::POST),
"PUT" => Ok(reqwest::Method::PUT),
"DELETE" => Ok(reqwest::Method::DELETE),
other => Err(AriError::WebSocket(format!(
"unsupported HTTP method: {other}"
))),
}
}