Skip to main content

workflow_wasm/
convert.rs

1//!
2//! WASM bindgen casting and utility functions.
3//!
4//! This module provides a `CastFromJs` trait and derive macro
5//! that allows for easy casting of JavaScript objects into Rust.
6//! The secondary goal of this module is to provide the ability
7//! to dynamically interpret user-supplied JavaScript data that
8//! instead of a Rust object may container other data that can
9//! be used (interpreted) to create a Rust object.
10//!
11//! To accommodate this a [`TryCastFromJs`] trait is provided
12//! where user needs to implement `try_cast_from` function that
13//! can attempt to cast a JsValue into a Rust object or interpret
14//! the source data and create a temporary struct owned by by the
15//! [`Cast`] enum.
16//!
17//!
18
19use crate::error::Error;
20use crate::extensions::ObjectExtension;
21use js_sys::Object;
22pub use std::borrow::Borrow;
23pub use std::ops::Deref;
24use wasm_bindgen::__rt::{WasmPtr, WasmRefCell};
25use wasm_bindgen::convert::{LongRefFromWasmAbi, RefFromWasmAbi, RefMutFromWasmAbi};
26use wasm_bindgen::prelude::*;
27pub use workflow_wasm_macros::CastFromJs;
28
29#[wasm_bindgen(typescript_custom_section)]
30const IWASM32_BINDINGS_CONFIG: &'static str = r#"
31/**
32 * Interface for configuring workflow-rs WASM32 bindings.
33 * 
34 * @category General
35 */
36export interface IWASM32BindingsConfig {
37    /**
38     * This option can be used to disable the validation of class names
39     * for instances of classes exported by Rust WASM32 when passing
40     * these classes to WASM32 functions.
41     * 
42     * This can be useful to programmatically disable checks when using
43     * a bundler that mangles class symbol names.
44     */
45    validateClassNames : boolean;
46}
47"#;
48
49#[wasm_bindgen]
50extern "C" {
51    /// JavaScript configuration object for the workflow-rs WASM32 bindings runtime.
52    #[wasm_bindgen(extends = Object, typescript_type = "IWASM32BindingsConfig")]
53    pub type IWASM32BindingsConfig;
54}
55
56static mut VALIDATE_CLASS_NAMES: bool = true;
57/// Configuration for the WASM32 bindings runtime interface.
58/// @see {@link IWASM32BindingsConfig}
59/// @category General
60#[wasm_bindgen(js_name = "initWASM32Bindings")]
61pub fn init_wasm32_bindings(config: IWASM32BindingsConfig) -> std::result::Result<(), Error> {
62    if let Some(enable) = config.try_get_bool("validateClassNames")? {
63        unsafe {
64            VALIDATE_CLASS_NAMES = enable;
65        }
66    }
67    Ok(())
68}
69/// Returns whether runtime validation of WASM class names is currently enabled.
70#[inline(always)]
71pub fn validate_class_names() -> bool {
72    unsafe { VALIDATE_CLASS_NAMES }
73}
74
75/// A wrapper for a Rust object that can be either a reference or a value.
76/// This wrapper is used to carry a Rust (WASM ABI) reference provided by
77/// `wasm_bindgen`, but at the same time allows creation of a temporary
78/// object that can be created by interpreting the source user-supplied data.
79/// [`Cast`] then provides [`Cast::as_ref()`] to obtain the internally held
80/// reference and [`Cast::into_owned()`] where the latter will consume the
81/// value or clone the reference.
82pub enum Cast<'a, T>
83where
84    T: RefFromWasmAbi<Abi = WasmPtr<WasmRefCell<T>>>
85        + LongRefFromWasmAbi<Abi = WasmPtr<WasmRefCell<T>>>
86        + 'a,
87{
88    /// A borrowed reference to a Rust object obtained from the WASM ABI.
89    Ref {
90        /// Anchor keeping the borrowed reference alive.
91        anchor: <T as RefFromWasmAbi>::Anchor,
92    },
93    /// A reference that also owns (captures) the source `JsValue` keeping it alive.
94    OwnedRef {
95        /// The captured source `JsValue` backing the reference.
96        js_value: Option<JsValue>,
97        /// Anchor keeping the borrowed reference alive.
98        anchor: Option<<T as RefFromWasmAbi>::Anchor>,
99    },
100    /// A long-lived reference to a Rust object obtained from the WASM ABI.
101    LongRef {
102        /// Anchor keeping the long-lived reference alive.
103        anchor: <T as LongRefFromWasmAbi>::Anchor,
104    },
105    /// An owned value, typically created by interpreting user-supplied data.
106    Value {
107        /// The owned value.
108        value: Option<T>,
109    },
110    /// Uninhabited variant carrying the lifetime parameter; never constructed.
111    _Unreachable(std::convert::Infallible, &'a std::marker::PhantomData<T>),
112}
113
114impl<T> Drop for Cast<'_, T>
115where
116    T: RefFromWasmAbi<Abi = WasmPtr<WasmRefCell<T>>>
117        + LongRefFromWasmAbi<Abi = WasmPtr<WasmRefCell<T>>>,
118{
119    fn drop(&mut self) {
120        if let Cast::OwnedRef { js_value, anchor } = self {
121            // ensure anchor is dropped before js_value
122            // as anchor holds a borrow, while js_value Drop impl requires a borrow
123            drop(anchor.take());
124            drop(js_value.take());
125        }
126    }
127}
128
129impl<T> Deref for Cast<'_, T>
130where
131    T: RefFromWasmAbi<Abi = WasmPtr<WasmRefCell<T>>>
132        + LongRefFromWasmAbi<Abi = WasmPtr<WasmRefCell<T>>>
133        + Deref,
134{
135    type Target = T;
136    fn deref(&self) -> &Self::Target {
137        match self {
138            Cast::Ref { anchor } => anchor,
139            Cast::OwnedRef { anchor, .. } => anchor.as_ref().unwrap(),
140            Cast::LongRef { anchor } => anchor.borrow(),
141            Cast::Value { value } => value.as_ref().unwrap(),
142            Cast::_Unreachable(_, _) => unreachable!(),
143        }
144    }
145}
146
147impl<T> AsRef<T> for Cast<'_, T>
148where
149    T: RefFromWasmAbi<Abi = WasmPtr<WasmRefCell<T>>>
150        + LongRefFromWasmAbi<Abi = WasmPtr<WasmRefCell<T>>>,
151{
152    /// Obtain a reference to the internally held value.
153    fn as_ref(&self) -> &T {
154        match self {
155            Cast::Ref { anchor } => anchor,
156            Cast::OwnedRef { anchor, .. } => anchor.as_ref().unwrap(),
157            Cast::LongRef { anchor } => anchor.borrow(),
158            Cast::Value { value } => value.as_ref().unwrap(),
159            Cast::_Unreachable(_, _) => unreachable!(),
160        }
161    }
162}
163
164impl<T> Cast<'_, T>
165where
166    T: RefFromWasmAbi<Abi = WasmPtr<WasmRefCell<T>>>
167        + LongRefFromWasmAbi<Abi = WasmPtr<WasmRefCell<T>>>
168        + Clone,
169{
170    /// Consume the [`Cast`] and return the owned value. If the
171    /// [`Cast`] holds a reference, it will be cloned.
172    pub fn into_owned(mut self) -> T {
173        match &mut self {
174            Cast::Ref { anchor } => (*anchor).clone(),
175            Cast::OwnedRef { js_value, anchor } => {
176                let value = (*anchor.as_ref().unwrap()).clone();
177                drop(anchor.take());
178                drop(js_value.take());
179                value
180            }
181            Cast::LongRef { anchor } => (*anchor).borrow().clone(),
182            Cast::Value { value } => value.take().unwrap(),
183            Cast::_Unreachable(_, _) => unreachable!(),
184        }
185    }
186
187    /// Construct a [`Cast::Value`] holding an owned value.
188    pub fn value(value: T) -> Self {
189        Cast::Value { value: Some(value) }
190    }
191
192    // pub fn captured_ref(js_value : impl AsRef<JsValue>)
193}
194
195/// Cast T value (struct) into `Cast<T>`
196impl<'a, T> From<T> for Cast<'a, T>
197where
198    T: RefFromWasmAbi<Abi = WasmPtr<WasmRefCell<T>>>
199        + LongRefFromWasmAbi<Abi = WasmPtr<WasmRefCell<T>>>,
200{
201    fn from(value: T) -> Cast<'a, T> {
202        Cast::Value { value: Some(value) }
203    }
204}
205
206/// `CastFromJs` trait is automatically implemented by deriving
207/// the `CastFromJs` derive macro. This trait provides functions
208/// for accessing Rust references from the WASM ABI.
209pub trait CastFromJs
210where
211    Self: Sized
212        + RefFromWasmAbi<Abi = WasmPtr<WasmRefCell<Self>>>
213        + LongRefFromWasmAbi<Abi = WasmPtr<WasmRefCell<Self>>>,
214{
215    /// Obtain safe reference from [`JsValue`]
216    fn try_ref_from_js_value<'a, R>(
217        js_value: &'a R,
218    ) -> std::result::Result<<Self as RefFromWasmAbi>::Anchor, Error>
219    where
220        R: AsRef<JsValue> + 'a;
221
222    /// Obtain a safe reference from a [`JsValue`], wrapped in a [`Cast::Ref`].
223    fn try_ref_from_js_value_as_cast<'a, R>(
224        js_value: &'a R,
225    ) -> std::result::Result<Cast<'a, Self>, Error>
226    where
227        R: AsRef<JsValue> + 'a,
228    {
229        Self::try_ref_from_js_value(js_value).map(|anchor| Cast::Ref { anchor })
230    }
231
232    /// Obtain safe long reference from [`JsValue`]
233    fn try_long_ref_from_js_value<'a, R>(
234        js: &'a R,
235    ) -> std::result::Result<<Self as LongRefFromWasmAbi>::Anchor, Error>
236    where
237        R: AsRef<JsValue> + 'a;
238
239    /// Obtain a safe long-lived reference from a [`JsValue`], wrapped in a [`Cast::LongRef`].
240    fn try_long_ref_from_js_value_as_cast<'a, R>(
241        js: &'a R,
242    ) -> std::result::Result<Cast<'a, Self>, Error>
243    where
244        R: AsRef<JsValue> + 'a,
245    {
246        Self::try_long_ref_from_js_value(js).map(|anchor| Cast::LongRef { anchor })
247    }
248}
249
250/// `TryCastFromJs` trait is meant to be implemented by the developer
251/// on any struct implementing `CastFromJs` trait. This trait provides
252/// a way to attempt to cast a JsValue into a Rust object or interpret
253/// the source data and create a temporary struct owned by by the [`Cast`].
254pub trait TryCastFromJs
255where
256    Self: CastFromJs
257        + RefFromWasmAbi<Abi = WasmPtr<WasmRefCell<Self>>>
258        + LongRefFromWasmAbi<Abi = WasmPtr<WasmRefCell<Self>>>
259        + Clone,
260{
261    /// Error type returned by the cast, displayable and convertible from [`Error`].
262    type Error: std::fmt::Display + From<Error>;
263
264    /// Try to cast a JsValue into a Rust object.
265    /// This should be user-defined function that
266    /// attempts to cast a JsValue into a Rust object
267    /// or interpret a source data and create a
268    /// temporary struct owned by by the [`Cast`].
269    fn try_cast_from<'a, R>(value: &'a R) -> std::result::Result<Cast<'a, Self>, Self::Error>
270    where
271        R: AsRef<JsValue> + 'a;
272
273    /// Perform a user cast and consume the [`Cast`] container.
274    /// This function will return a temporary user-created
275    /// object created during `try_cast_from` or a clone of the casted reference.
276    fn try_owned_from(value: impl AsRef<JsValue>) -> std::result::Result<Self, Self::Error> {
277        Self::try_cast_from(&value).map(|c| c.into_owned())
278    }
279
280    /// Cast a `JsValue` into a Rust object while capturing (owning) the source
281    /// `JsValue`, yielding a `'static` [`Cast`] that keeps the reference alive.
282    fn try_captured_cast_from(
283        js_value: impl AsRef<JsValue>,
284    ) -> std::result::Result<Cast<'static, Self>, Self::Error> {
285        let js_value = js_value.as_ref().clone();
286        Ok(
287            Self::try_ref_from_js_value(&js_value).map(|anchor| Cast::OwnedRef {
288                js_value: Some(js_value),
289                anchor: Some(anchor),
290            })?,
291        )
292    }
293
294    /// Try to cast a JsValue into a Rust object, in cast of failure
295    /// invoke a user-supplied closure that can try to create an instance
296    /// of the object based on the supplied JsValue.
297    fn resolve<'a, R>(
298        js: &'a R,
299        create: impl FnOnce() -> std::result::Result<Self, Self::Error>,
300    ) -> std::result::Result<Cast<'a, Self>, Self::Error>
301    where
302        R: AsRef<JsValue> + 'a,
303    {
304        Self::try_ref_from_js_value(js)
305            .map(|anchor| Cast::Ref { anchor })
306            .or_else(|_| create().map(|value| Cast::Value { value: Some(value) }))
307    }
308
309    /// Try to cast a JsValue into a Rust object, in cast of failure
310    /// invoke a user-supplied closure that can try to create an instance
311    /// of the object based on the supplied JsValue. Unlike the `resolve`
312    /// function, this function expects `create` closure to return a [`Cast`].
313    /// This is useful when routing the creation of the object to another
314    /// function that is capable of creating a compatible Cast wrapper.
315    fn resolve_cast<'a, R>(
316        js: &'a R,
317        create: impl FnOnce() -> std::result::Result<Cast<'a, Self>, Self::Error>,
318    ) -> std::result::Result<Cast<'a, Self>, Self::Error>
319    where
320        R: AsRef<JsValue> + 'a,
321    {
322        Self::try_ref_from_js_value(js)
323            .map(|anchor| Cast::Ref { anchor })
324            .or_else(|_| create())
325    }
326}
327
328/// Inverse of [`TryCastFromJs`], allowing a source value (such as a [`JsValue`])
329/// to be cast into a target Rust type `T` that implements [`TryCastFromJs`].
330pub trait TryCastJsInto<T>
331where
332    T: TryCastFromJs,
333{
334    /// Error type returned by the conversion, convertible from [`Error`].
335    type Error: From<Error>;
336    /// Attempt to cast `self` into a [`Cast`] wrapping the target type `T`.
337    fn try_into_cast(&self) -> std::result::Result<Cast<'_, T>, Self::Error>;
338    /// Attempt to cast `self` into an owned value of the target type `T`.
339    fn try_into_owned(&self) -> std::result::Result<T, Self::Error>;
340}
341
342impl<T> TryCastJsInto<T> for JsValue
343where
344    T: TryCastFromJs,
345    <T as TryCastFromJs>::Error: From<Error>,
346{
347    type Error = <T as TryCastFromJs>::Error;
348    fn try_into_cast(&self) -> std::result::Result<Cast<'_, T>, Self::Error> {
349        T::try_cast_from(self)
350    }
351
352    fn try_into_owned(&self) -> std::result::Result<T, Self::Error> {
353        T::try_owned_from(self)
354    }
355}
356
357/// Obtain a WASM bingen ABI pointer from a supplied JsValue.
358/// This function validates the acquired object ptr by comparing its
359/// `constructor.name` value to the supplied `class` name.
360fn get_ptr_u32_safe(
361    class: &str,
362    js: impl AsRef<JsValue>,
363) -> std::result::Result<Option<u32>, Error> {
364    let js = js.as_ref();
365
366    if js.is_undefined() || js.is_null() {
367        return Ok(None);
368    } else if !js.is_object() {
369        return Err(Error::NotAnObjectOfClass(class.to_string()));
370    }
371
372    if validate_class_names() {
373        let ctor = ::js_sys::Reflect::get(js, &JsValue::from_str("constructor"))?;
374        if ctor.is_undefined() {
375            return Err(Error::NoConstructorOfClass(class.to_string()));
376        } else {
377            let name = ::js_sys::Reflect::get(&ctor, &JsValue::from_str("name"))?;
378            if name.is_undefined() {
379                return Err(Error::UnableToObtainConstructorName(class.to_string()));
380            } else {
381                let name = name
382                    .as_string()
383                    .ok_or(Error::UnableToObtainConstructorName(class.to_string()))?;
384                if name != class {
385                    return Err(Error::ClassConstructorMatch(name, class.to_string()));
386                }
387            }
388        }
389    }
390
391    let ptr = ::js_sys::Reflect::get(js, &::wasm_bindgen::JsValue::from_str("__wbg_ptr"))?;
392    if ptr.is_undefined() {
393        return Err(Error::NotWasmAbiPointerForClass(class.to_string()));
394    }
395    let ptr_u32: u32 = ptr
396        .as_f64()
397        .ok_or(Error::NotWasmAbiPointerForClass(class.to_string()))? as u32;
398
399    Ok(Some(ptr_u32))
400}
401
402/// Create a reference to a Rust object from a WASM ABI.
403#[inline]
404pub fn try_ref_from_abi_safe<T>(
405    class: &str,
406    js: impl AsRef<JsValue>,
407) -> std::result::Result<<T as RefFromWasmAbi>::Anchor, Error>
408where
409    T: RefFromWasmAbi<Abi = WasmPtr<WasmRefCell<T>>>,
410{
411    let ptr_u32 =
412        get_ptr_u32_safe(class, js)?.ok_or_else(|| Error::NotAnObjectOfClass(class.to_string()))?;
413    Ok(unsafe { T::ref_from_abi(WasmPtr::from_usize(ptr_u32 as usize)) })
414}
415
416#[inline]
417/// Create a long-lived reference to a Rust object from a WASM ABI obtained from a `JsValue`.
418pub fn try_long_ref_from_abi_safe<T>(
419    class: &str,
420    js: impl AsRef<JsValue>,
421) -> std::result::Result<<T as LongRefFromWasmAbi>::Anchor, Error>
422where
423    T: LongRefFromWasmAbi<Abi = WasmPtr<WasmRefCell<T>>>,
424{
425    let ptr_u32 =
426        get_ptr_u32_safe(class, js)?.ok_or_else(|| Error::NotAnObjectOfClass(class.to_string()))?;
427    Ok(unsafe { T::long_ref_from_abi(WasmPtr::from_usize(ptr_u32 as usize)) })
428}
429
430#[inline]
431/// Create a mutable reference to a Rust object from a WASM ABI obtained from a `JsValue`.
432pub fn try_ref_mut_from_abi_safe<T>(
433    class: &str,
434    js: impl AsRef<JsValue>,
435) -> std::result::Result<<T as RefMutFromWasmAbi>::Anchor, Error>
436where
437    T: RefMutFromWasmAbi<Abi = WasmPtr<WasmRefCell<T>>>,
438{
439    let ptr_u32 =
440        get_ptr_u32_safe(class, js)?.ok_or_else(|| Error::NotAnObjectOfClass(class.to_string()))?;
441    Ok(unsafe { T::ref_mut_from_abi(WasmPtr::from_usize(ptr_u32 as usize)) })
442}
443
444#[inline]
445/// Clone a Rust object out of a WASM ABI reference obtained from a `JsValue`.
446pub fn try_clone_from_abi_safe<T>(
447    class: &str,
448    js: impl AsRef<JsValue>,
449) -> std::result::Result<T, Error>
450where
451    T: RefFromWasmAbi<Abi = WasmPtr<WasmRefCell<T>>> + Clone,
452{
453    try_ref_from_abi_safe::<T>(class, js).map(|r| r.clone())
454}
455
456#[inline]
457/// Copy a Rust object out of a WASM ABI reference obtained from a `JsValue`.
458pub fn try_copy_from_abi_safe<T>(
459    class: &str,
460    js: impl AsRef<JsValue>,
461) -> std::result::Result<T, Error>
462where
463    T: RefFromWasmAbi<Abi = WasmPtr<WasmRefCell<T>>> + Copy,
464{
465    try_ref_from_abi_safe::<T>(class, js).map(|r| *r)
466}
467
468/// Create a reference to a Rust object from a WASM ABI.
469/// Returns None is the supplied value is `null` or `undefined`,
470/// otherwise attempts to cast the object.
471#[inline]
472pub fn try_ref_from_abi_safe_as_option<T>(
473    class: &str,
474    js: impl AsRef<JsValue>,
475) -> std::result::Result<Option<<T as RefFromWasmAbi>::Anchor>, JsValue>
476where
477    T: RefFromWasmAbi<Abi = WasmPtr<WasmRefCell<T>>>,
478{
479    Ok(get_ptr_u32_safe(class, js)?
480        .map(|ptr_u32| unsafe { T::ref_from_abi(WasmPtr::from_usize(ptr_u32 as usize)) }))
481}
482
483#[inline]
484/// Create a mutable reference to a Rust object from a WASM ABI obtained from a `JsValue`,
485/// returning `None` if the value is `null` or `undefined`.
486pub fn try_ref_mut_from_abi_safe_as_option<T>(
487    class: &str,
488    js: impl AsRef<JsValue>,
489) -> std::result::Result<Option<<T as RefMutFromWasmAbi>::Anchor>, JsValue>
490where
491    T: RefMutFromWasmAbi<Abi = WasmPtr<WasmRefCell<T>>>,
492{
493    Ok(get_ptr_u32_safe(class, js)?
494        .map(|ptr_u32| unsafe { T::ref_mut_from_abi(WasmPtr::from_usize(ptr_u32 as usize)) }))
495}
496
497#[inline]
498/// Clone a Rust object out of a WASM ABI reference obtained from a `JsValue`,
499/// returning `None` if the value is `null` or `undefined`.
500pub fn try_clone_from_abi_safe_as_option<T>(
501    class: &str,
502    js: impl AsRef<JsValue>,
503) -> std::result::Result<Option<T>, JsValue>
504where
505    T: RefFromWasmAbi<Abi = WasmPtr<WasmRefCell<T>>> + Clone,
506{
507    Ok(get_ptr_u32_safe(class, js)?
508        .map(|ptr_u32| unsafe { T::ref_from_abi(WasmPtr::from_usize(ptr_u32 as usize)).clone() }))
509}
510
511#[inline]
512/// Copy a Rust object out of a WASM ABI reference obtained from a `JsValue`,
513/// returning `None` if the value is `null` or `undefined`.
514pub fn try_copy_from_abi_safe_as_option<T>(
515    class: &str,
516    js: impl AsRef<JsValue>,
517) -> std::result::Result<Option<T>, JsValue>
518where
519    T: RefFromWasmAbi<Abi = WasmPtr<WasmRefCell<T>>> + Copy,
520{
521    Ok(get_ptr_u32_safe(class, js)?
522        .map(|ptr_u32| unsafe { *T::ref_from_abi(WasmPtr::from_usize(ptr_u32 as usize)) }))
523}