use crate::error::Error;
use crate::extensions::ObjectExtension;
use js_sys::Object;
pub use std::borrow::Borrow;
pub use std::ops::Deref;
use wasm_bindgen::convert::{LongRefFromWasmAbi, RefFromWasmAbi, RefMutFromWasmAbi};
use wasm_bindgen::prelude::*;
pub use workflow_wasm_macros::CastFromJs;
#[wasm_bindgen(typescript_custom_section)]
const IWASM32_BINDINGS_CONFIG: &'static str = r#"
/**
* Interface for configuring workflow-rs WASM32 bindings.
*
* @category General
*/
export interface IWASM32BindingsConfig {
/**
* This option can be used to disable the validation of class names
* for instances of classes exported by Rust WASM32 when passing
* these classes to WASM32 functions.
*
* This can be useful to programmatically disable checks when using
* a bundler that mangles class symbol names.
*/
validateClassNames : boolean;
}
"#;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(extends = Object, typescript_type = "IWASM32BindingsConfig")]
pub type IWASM32BindingsConfig;
}
static mut VALIDATE_CLASS_NAMES: bool = true;
#[wasm_bindgen(js_name = "initWASM32Bindings")]
pub fn init_wasm32_bindings(config: IWASM32BindingsConfig) -> std::result::Result<(), Error> {
if let Some(enable) = config.try_get_bool("validateClassNames")? {
unsafe {
VALIDATE_CLASS_NAMES = enable;
}
}
Ok(())
}
#[inline(always)]
pub fn validate_class_names() -> bool {
unsafe { VALIDATE_CLASS_NAMES }
}
pub enum Cast<'a, T>
where
T: RefFromWasmAbi<Abi = u32> + LongRefFromWasmAbi<Abi = u32> + 'a,
{
Ref {
anchor: <T as RefFromWasmAbi>::Anchor,
},
OwnedRef {
js_value: Option<JsValue>,
anchor: Option<<T as RefFromWasmAbi>::Anchor>,
},
LongRef {
anchor: <T as LongRefFromWasmAbi>::Anchor,
},
Value {
value: Option<T>,
},
_Unreachable(std::convert::Infallible, &'a std::marker::PhantomData<T>),
}
impl<'a, T> Drop for Cast<'a, T>
where
T: RefFromWasmAbi<Abi = u32> + LongRefFromWasmAbi<Abi = u32> + 'a,
{
fn drop(&mut self) {
if let Cast::OwnedRef { js_value, anchor } = self {
drop(anchor.take());
drop(js_value.take());
}
}
}
impl<'a, T> Deref for Cast<'a, T>
where
T: RefFromWasmAbi<Abi = u32> + LongRefFromWasmAbi<Abi = u32> + Deref,
{
type Target = T;
fn deref(&self) -> &Self::Target {
match self {
Cast::Ref { anchor } => anchor,
Cast::OwnedRef { anchor, .. } => anchor.as_ref().unwrap(),
Cast::LongRef { anchor } => anchor.borrow(),
Cast::Value { value } => value.as_ref().unwrap(),
Cast::_Unreachable(_, _) => unreachable!(),
}
}
}
impl<'a, T> AsRef<T> for Cast<'a, T>
where
T: RefFromWasmAbi<Abi = u32> + LongRefFromWasmAbi<Abi = u32>,
{
fn as_ref(&self) -> &T {
match self {
Cast::Ref { anchor } => anchor,
Cast::OwnedRef { anchor, .. } => anchor.as_ref().unwrap(),
Cast::LongRef { anchor } => anchor.borrow(),
Cast::Value { value } => value.as_ref().unwrap(),
Cast::_Unreachable(_, _) => unreachable!(),
}
}
}
impl<'a, T> Cast<'a, T>
where
T: RefFromWasmAbi<Abi = u32> + LongRefFromWasmAbi<Abi = u32> + Clone,
{
pub fn into_owned(mut self) -> T {
match &mut self {
Cast::Ref { anchor } => (*anchor).clone(),
Cast::OwnedRef { js_value, anchor } => {
let value = (*anchor.as_ref().unwrap()).clone();
drop(anchor.take());
drop(js_value.take());
value
}
Cast::LongRef { anchor } => (*anchor).borrow().clone(),
Cast::Value { value } => value.take().unwrap(),
Cast::_Unreachable(_, _) => unreachable!(),
}
}
pub fn value(value: T) -> Self {
Cast::Value { value: Some(value) }
}
}
impl<'a, T> From<T> for Cast<'a, T>
where
T: RefFromWasmAbi<Abi = u32> + LongRefFromWasmAbi<Abi = u32>,
{
fn from(value: T) -> Cast<'a, T> {
Cast::Value { value: Some(value) }
}
}
pub trait CastFromJs
where
Self: Sized + RefFromWasmAbi<Abi = u32> + LongRefFromWasmAbi<Abi = u32>,
{
fn try_ref_from_js_value<'a, R>(
js_value: &'a R,
) -> std::result::Result<<Self as RefFromWasmAbi>::Anchor, Error>
where
R: AsRef<JsValue> + 'a;
fn try_ref_from_js_value_as_cast<'a, R>(
js_value: &'a R,
) -> std::result::Result<Cast<'a, Self>, Error>
where
R: AsRef<JsValue> + 'a,
{
Self::try_ref_from_js_value(js_value).map(|anchor| Cast::Ref { anchor })
}
fn try_long_ref_from_js_value<'a, R>(
js: &'a R,
) -> std::result::Result<<Self as LongRefFromWasmAbi>::Anchor, Error>
where
R: AsRef<JsValue> + 'a;
fn try_long_ref_from_js_value_as_cast<'a, R>(
js: &'a R,
) -> std::result::Result<Cast<Self>, Error>
where
R: AsRef<JsValue> + 'a,
{
Self::try_long_ref_from_js_value(js).map(|anchor| Cast::LongRef { anchor })
}
}
pub trait TryCastFromJs
where
Self: CastFromJs + RefFromWasmAbi<Abi = u32> + LongRefFromWasmAbi<Abi = u32> + Clone,
{
type Error: std::fmt::Display + From<Error>;
fn try_cast_from<'a, R>(value: &'a R) -> std::result::Result<Cast<'a, Self>, Self::Error>
where
R: AsRef<JsValue> + 'a;
fn try_owned_from(value: impl AsRef<JsValue>) -> std::result::Result<Self, Self::Error> {
Self::try_cast_from(&value).map(|c| c.into_owned())
}
fn try_captured_cast_from(
js_value: impl AsRef<JsValue>,
) -> std::result::Result<Cast<'static, Self>, Self::Error> {
let js_value = js_value.as_ref().clone();
Ok(
Self::try_ref_from_js_value(&js_value).map(|anchor| Cast::OwnedRef {
js_value: Some(js_value),
anchor: Some(anchor),
})?,
)
}
fn resolve<'a, R>(
js: &'a R,
create: impl FnOnce() -> std::result::Result<Self, Self::Error>,
) -> std::result::Result<Cast<Self>, Self::Error>
where
R: AsRef<JsValue> + 'a,
{
Self::try_ref_from_js_value(js)
.map(|anchor| Cast::Ref { anchor })
.or_else(|_| create().map(|value| Cast::Value { value: Some(value) }))
}
fn resolve_cast<'a, R>(
js: &'a R,
create: impl FnOnce() -> std::result::Result<Cast<'a, Self>, Self::Error>,
) -> std::result::Result<Cast<'a, Self>, Self::Error>
where
R: AsRef<JsValue> + 'a,
{
Self::try_ref_from_js_value(js)
.map(|anchor| Cast::Ref { anchor })
.or_else(|_| create())
}
}
pub trait TryCastJsInto<T>
where
T: TryCastFromJs,
{
type Error: From<Error>;
fn try_into_cast(&self) -> std::result::Result<Cast<T>, Self::Error>;
fn try_into_owned(&self) -> std::result::Result<T, Self::Error>;
}
impl<T> TryCastJsInto<T> for JsValue
where
T: TryCastFromJs,
<T as TryCastFromJs>::Error: From<Error>,
{
type Error = <T as TryCastFromJs>::Error;
fn try_into_cast(&self) -> std::result::Result<Cast<T>, Self::Error> {
T::try_cast_from(self)
}
fn try_into_owned(&self) -> std::result::Result<T, Self::Error> {
T::try_owned_from(self)
}
}
fn get_ptr_u32_safe(
class: &str,
js: impl AsRef<JsValue>,
) -> std::result::Result<Option<u32>, Error> {
let js = js.as_ref();
if js.is_undefined() || js.is_null() {
return Ok(None);
} else if !js.is_object() {
return Err(Error::NotAnObjectOfClass(class.to_string()));
}
if validate_class_names() {
let ctor = ::js_sys::Reflect::get(js, &JsValue::from_str("constructor"))?;
if ctor.is_undefined() {
return Err(Error::NoConstructorOfClass(class.to_string()));
} else {
let name = ::js_sys::Reflect::get(&ctor, &JsValue::from_str("name"))?;
if name.is_undefined() {
return Err(Error::UnableToObtainConstructorName(class.to_string()));
} else {
let name = name
.as_string()
.ok_or(Error::UnableToObtainConstructorName(class.to_string()))?;
if name != class {
return Err(Error::ClassConstructorMatch(name, class.to_string()));
}
}
}
}
let ptr = ::js_sys::Reflect::get(js, &::wasm_bindgen::JsValue::from_str("__wbg_ptr"))?;
if ptr.is_undefined() {
return Err(Error::NotWasmAbiPointerForClass(class.to_string()));
}
let ptr_u32: u32 = ptr
.as_f64()
.ok_or(Error::NotWasmAbiPointerForClass(class.to_string()))? as u32;
Ok(Some(ptr_u32))
}
#[inline]
pub fn try_ref_from_abi_safe<T>(
class: &str,
js: impl AsRef<JsValue>,
) -> std::result::Result<<T as RefFromWasmAbi>::Anchor, Error>
where
T: RefFromWasmAbi<Abi = u32>,
{
let ptr_u32 =
get_ptr_u32_safe(class, js)?.ok_or_else(|| Error::NotAnObjectOfClass(class.to_string()))?;
Ok(unsafe { T::ref_from_abi(ptr_u32) })
}
#[inline]
pub fn try_long_ref_from_abi_safe<T>(
class: &str,
js: impl AsRef<JsValue>,
) -> std::result::Result<<T as LongRefFromWasmAbi>::Anchor, Error>
where
T: LongRefFromWasmAbi<Abi = u32>,
{
let ptr_u32 =
get_ptr_u32_safe(class, js)?.ok_or_else(|| Error::NotAnObjectOfClass(class.to_string()))?;
Ok(unsafe { T::long_ref_from_abi(ptr_u32) })
}
#[inline]
pub fn try_ref_mut_from_abi_safe<T>(
class: &str,
js: impl AsRef<JsValue>,
) -> std::result::Result<<T as RefMutFromWasmAbi>::Anchor, Error>
where
T: RefMutFromWasmAbi<Abi = u32>,
{
let ptr_u32 =
get_ptr_u32_safe(class, js)?.ok_or_else(|| Error::NotAnObjectOfClass(class.to_string()))?;
Ok(unsafe { T::ref_mut_from_abi(ptr_u32) })
}
#[inline]
pub fn try_clone_from_abi_safe<T>(
class: &str,
js: impl AsRef<JsValue>,
) -> std::result::Result<T, Error>
where
T: RefFromWasmAbi<Abi = u32> + Clone,
{
try_ref_from_abi_safe::<T>(class, js).map(|r| r.clone())
}
#[inline]
pub fn try_copy_from_abi_safe<T>(
class: &str,
js: impl AsRef<JsValue>,
) -> std::result::Result<T, Error>
where
T: RefFromWasmAbi<Abi = u32> + Copy,
{
try_ref_from_abi_safe::<T>(class, js).map(|r| *r)
}
#[inline]
pub fn try_ref_from_abi_safe_as_option<T>(
class: &str,
js: impl AsRef<JsValue>,
) -> std::result::Result<Option<<T as RefFromWasmAbi>::Anchor>, JsValue>
where
T: RefFromWasmAbi<Abi = u32>,
{
Ok(get_ptr_u32_safe(class, js)?.map(|ptr_u32| unsafe { T::ref_from_abi(ptr_u32) }))
}
#[inline]
pub fn try_ref_mut_from_abi_safe_as_option<T>(
class: &str,
js: impl AsRef<JsValue>,
) -> std::result::Result<Option<<T as RefMutFromWasmAbi>::Anchor>, JsValue>
where
T: RefMutFromWasmAbi<Abi = u32>,
{
Ok(get_ptr_u32_safe(class, js)?.map(|ptr_u32| unsafe { T::ref_mut_from_abi(ptr_u32) }))
}
#[inline]
pub fn try_clone_from_abi_safe_as_option<T>(
class: &str,
js: impl AsRef<JsValue>,
) -> std::result::Result<Option<T>, JsValue>
where
T: RefFromWasmAbi<Abi = u32> + Clone,
{
Ok(get_ptr_u32_safe(class, js)?.map(|ptr_u32| unsafe { T::ref_from_abi(ptr_u32).clone() }))
}
#[inline]
pub fn try_copy_from_abi_safe_as_option<T>(
class: &str,
js: impl AsRef<JsValue>,
) -> std::result::Result<Option<T>, JsValue>
where
T: RefFromWasmAbi<Abi = u32> + Copy,
{
Ok(get_ptr_u32_safe(class, js)?.map(|ptr_u32| unsafe { *T::ref_from_abi(ptr_u32) }))
}