use crate::CancellationToken;
use std::fmt;
use wasm_bindgen::{JsCast, JsValue};
use wasm_bindgen_futures::JsFuture;
use web_sys::{RequestInit, Response};
#[derive(Debug)]
pub enum FetchError {
Cancelled,
Status { url: String, status: u16 },
Js(JsValue),
}
impl From<JsValue> for FetchError {
fn from(error: JsValue) -> Self {
Self::Js(error)
}
}
impl fmt::Display for FetchError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Cancelled => f.write_str("request cancelled"),
Self::Status { url, status } => write!(f, "GET {url}: HTTP {status}"),
Self::Js(error) => match error.as_string() {
Some(message) => f.write_str(&message),
None => write!(f, "{error:?}"),
},
}
}
}
impl std::error::Error for FetchError {}
pub async fn get_text(url: &str, cancel: &CancellationToken) -> Result<String, FetchError> {
let result: Result<String, FetchError> = async {
let window =
web_sys::window().ok_or_else(|| JsValue::from_str("Fetch requires a browser"))?;
let options = RequestInit::new();
options.set_signal(Some(&cancel.abort_signal()?));
let response: Response = JsFuture::from(window.fetch_with_str_and_init(url, &options))
.await?
.dyn_into()?;
if !response.ok() {
return Err(FetchError::Status {
url: url.to_owned(),
status: response.status(),
});
}
JsFuture::from(response.text()?)
.await?
.as_string()
.ok_or_else(|| JsValue::from_str("Fetch returned a non-text body").into())
}
.await;
result.map_err(|error| match error {
FetchError::Js(_) if cancel.is_cancelled() => FetchError::Cancelled,
error => error,
})
}