use std::future::Future;
use std::pin::Pin;
use wasm_bindgen::convert::{FromWasmAbi, IntoWasmAbi};
use wasm_bindgen::describe::WasmDescribe;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen::__rt::IntoJsResult;
use wasm_bindgen_futures::{future_to_promise, JsFuture};
use super::errors::*;
#[must_use]
pub struct ApiFuture<T>(Pin<Box<dyn Future<Output = ApiResult<T>>>>)
where
Result<T, JsValue>: IntoJsResult + 'static;
impl<T> ApiFuture<T>
where
Result<T, JsValue>: IntoJsResult + 'static,
{
pub fn new<U: Future<Output = ApiResult<T>> + 'static>(x: U) -> Self {
Self(Box::pin(x))
}
}
impl<T> ApiFuture<T>
where
Result<T, JsValue>: IntoJsResult + 'static,
{
pub fn spawn<U: Future<Output = ApiResult<T>> + 'static>(x: U) {
drop(js_sys::Promise::from(Self::new(x)))
}
}
impl<T> Default for ApiFuture<T>
where
Result<T, JsValue>: IntoJsResult + 'static,
T: Default,
{
fn default() -> Self {
Self::new(async { Ok(Default::default()) })
}
}
impl<T> From<ApiFuture<T>> for JsValue
where
Result<T, Self>: IntoJsResult + 'static,
{
fn from(fut: ApiFuture<T>) -> Self {
js_sys::Promise::from(fut).unchecked_into()
}
}
impl<T> From<ApiFuture<T>> for js_sys::Promise
where
Result<T, JsValue>: IntoJsResult + 'static,
{
fn from(fut: ApiFuture<T>) -> Self {
future_to_promise(async move {
let x = Ok(fut.0.await?);
let y = Ok(x.into_js_result()?);
Ok(y.ignore_view_delete()?)
})
}
}
impl<T> WasmDescribe for ApiFuture<T>
where
Result<T, JsValue>: IntoJsResult + 'static,
{
fn describe() {
<js_sys::Promise as WasmDescribe>::describe()
}
}
impl<T> IntoWasmAbi for ApiFuture<T>
where
Result<T, JsValue>: IntoJsResult + 'static,
{
type Abi = <js_sys::Promise as IntoWasmAbi>::Abi;
#[inline]
fn into_abi(self) -> Self::Abi {
js_sys::Promise::from(self).into_abi()
}
}
impl<T> FromWasmAbi for ApiFuture<T>
where
Result<T, JsValue>: IntoJsResult + 'static,
T: From<JsValue> + Into<JsValue>,
{
type Abi = <js_sys::Promise as IntoWasmAbi>::Abi;
#[inline]
unsafe fn from_abi(js: Self::Abi) -> Self {
Self::new(async move {
let promise = js_sys::Promise::from_abi(js);
Ok(JsFuture::from(promise).await?.into())
})
}
}
impl<T> Future for ApiFuture<T>
where
Result<T, JsValue>: IntoJsResult + 'static,
{
type Output = ApiResult<T>;
fn poll(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
let mut fut = unsafe { self.map_unchecked_mut(|s| &mut s.0) };
fut.as_mut().poll(cx)
}
}
static CANCELLED_MSG: &str = "View method cancelled";
#[extend::ext]
pub impl Result<JsValue, ApiError> {
fn ignore_view_delete(self) -> Result<JsValue, ApiError> {
self.or_else(|x| {
let f: JsValue = x.clone().into();
match f.dyn_ref::<js_sys::Error>() {
Some(err) => {
if err.message() != CANCELLED_MSG {
Err(x)
} else {
Ok(js_intern::js_intern!(CANCELLED_MSG).clone())
}
}
_ => match f.as_string() {
Some(x) if x == CANCELLED_MSG => {
Ok(js_intern::js_intern!(CANCELLED_MSG).clone())
}
Some(_) => Err(x),
_ => {
if js_sys::Reflect::get(&f, js_intern::js_intern!("message"))
.unwrap()
.as_string()
.unwrap_or_else(|| "".to_owned())
== CANCELLED_MSG
{
Ok(js_intern::js_intern!(CANCELLED_MSG).clone())
} else {
Err(x)
}
}
},
}
})
}
}