use std::future::Future;
use std::pin::Pin;
use perspective_client::ClientError;
use wasm_bindgen::__rt::IntoJsResult;
use wasm_bindgen::convert::{FromWasmAbi, IntoWasmAbi};
use wasm_bindgen::describe::WasmDescribe;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::{JsFuture, future_to_promise};
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))
}
pub fn new_throttled<U: Future<Output = ApiResult<T>> + 'static>(x: U) -> ApiFuture<()> {
ApiFuture::<()>(Box::pin(
async move { x.await.ignore_view_delete().map(|_| ()) },
))
}
}
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)))
}
pub fn spawn_throttled<U: Future<Output = ApiResult<T>> + 'static>(x: U) {
drop(js_sys::Promise::from(Self::new_throttled(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 { Ok(fut.0.await?).into_js_result() })
}
}
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 = unsafe { 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)
}
}
#[extend::ext]
pub impl<T> Result<T, ApiError> {
fn ignore_view_delete(self) -> Result<Option<T>, ApiError> {
self.map(|x| Some(x)).or_else(|err| match err.inner() {
ApiErrorType::ClientError(ClientError::ViewNotFound) => Ok(None),
ApiErrorType::JsRawError(..) | ApiErrorType::JsError(..)
if format!("{err}").contains("View not found") =>
{
Ok(None)
},
_ => Err(err),
})
}
}