use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::time::Duration;
use ferrijs_fetch::{Body, ClientPool, Credentials, FetchError, Headers, NetGuard, NetPolicy, RedirectMode, Response};
#[derive(Debug)]
pub struct FetchRequest {
pub url: String,
pub method: String,
pub headers: Vec<(String, String)>,
pub body: Body,
pub redirect: RedirectMode,
pub credentials: Credentials,
pub net_guard: Option<NetGuard>,
pub timeout: Option<Duration>,
}
pub type FetchFuture<'a> = Pin<Box<dyn Future<Output = Result<Response, FetchError>> + Send + 'a>>;
pub trait FetchBackend: Send + Sync {
fn fetch(&self, request: FetchRequest) -> FetchFuture<'_>;
fn net_policy(&self, _ctx: &rquickjs::Ctx<'_>, realm: Arc<dyn NetPolicy>) -> Arc<dyn NetPolicy> {
realm
}
}
pub struct Client {
pool: ClientPool,
base_url: Option<String>,
default_headers: Vec<(String, String)>,
timeout: Duration,
}
impl std::fmt::Debug for Client {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Client")
.field("base_url", &self.base_url)
.field("default_headers", &self.default_headers)
.field("timeout", &self.timeout)
.finish_non_exhaustive()
}
}
impl Default for Client {
fn default() -> Self {
Self::new()
}
}
impl Client {
#[must_use]
pub fn new() -> Self {
Self {
pool: ClientPool::standalone(false),
base_url: None,
default_headers: Vec::new(),
timeout: Duration::from_secs(30),
}
}
#[must_use]
pub fn ignore_https_errors(mut self) -> Self {
self.pool = ClientPool::standalone(true);
self
}
#[must_use]
pub fn base_url(mut self, base: impl Into<String>) -> Self {
self.base_url = Some(base.into());
self
}
#[must_use]
pub fn default_headers(mut self, headers: Vec<(String, String)>) -> Self {
self.default_headers = headers;
self
}
#[must_use]
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
}
impl FetchBackend for Client {
fn fetch(&self, request: FetchRequest) -> FetchFuture<'_> {
Box::pin(async move {
let url = match (&self.base_url, ferrijs_fetch::reqwest::Url::parse(&request.url)) {
(_, Ok(url)) => url,
(Some(base), Err(_)) => ferrijs_fetch::reqwest::Url::parse(base)
.and_then(|b| b.join(&request.url))
.map_err(|e| FetchError::InvalidUrl(format!("cannot resolve {}: {e}", request.url)))?,
(None, Err(e)) => return Err(FetchError::InvalidUrl(format!("invalid URL {}: {e}", request.url))),
};
let method: ferrijs_fetch::reqwest::Method = request
.method
.parse()
.map_err(|_| FetchError::InvalidUrl(format!("invalid HTTP method: {}", request.method)))?;
let mut headers = Headers::new();
for (name, value) in &self.default_headers {
headers.set(name, value.clone());
}
for (name, value) in request.headers {
headers.set(&name, value);
}
ferrijs_fetch::send(
&self.pool,
None,
ferrijs_fetch::Request {
method,
url,
headers,
body: request.body,
redirect: request.redirect,
credentials: request.credentials,
max_redirects: None,
max_retries: 0,
timeout: request.timeout.unwrap_or(self.timeout),
ignore_https_errors: false,
net_guard: request.net_guard,
},
)
.await
})
}
}