Skip to main content

wry_bindgen/
cast.rs

1//! JsCast - Type casting trait for JavaScript types
2//!
3//! This trait provides methods for casting between JavaScript types,
4//! similar to wasm-bindgen's JsCast trait.
5
6use crate::{JsValue, convert::TryFromJsValue};
7
8/// Trait for types that can be cast to and from JsValue.
9///
10/// This is the wry-bindgen equivalent of wasm-bindgen's `JsCast` trait.
11/// It enables safe and unsafe casting between JavaScript types.
12pub trait JsCast
13where
14    Self: AsRef<JsValue> + Into<JsValue>,
15{
16    /// Check if a JsValue is an instance of this type.
17    ///
18    /// This performs a runtime instanceof check in JavaScript.
19    fn instanceof(val: &JsValue) -> bool;
20
21    /// Performs a dynamic type check to see whether the `JsValue` provided
22    /// is a value of this type.
23    ///
24    /// Unlike `instanceof`, this can be specialized to check for primitive types
25    /// or perform other type checks that aren't possible with instanceof.
26    /// The default implementation falls back to `instanceof`.
27    #[inline]
28    fn is_type_of(val: &JsValue) -> bool {
29        Self::instanceof(val)
30    }
31
32    /// Test whether this JS value has a type `T`.
33    ///
34    /// This method will dynamically check to see if this JS object can be
35    /// casted to the JS object of type `T`. Usually this uses the `instanceof`
36    /// operator, but can be customized with `is_type_of`. This also works
37    /// with primitive types like booleans/strings/numbers as well as cross-realm
38    /// objects like `Array` which can originate from other iframes.
39    ///
40    /// In general this is intended to be a more robust version of
41    /// `is_instance_of`, but if you want strictly the `instanceof` operator
42    /// it's recommended to use that instead.
43    #[inline]
44    fn has_type<T>(&self) -> bool
45    where
46        T: JsCast,
47    {
48        T::is_type_of(self.as_ref())
49    }
50
51    /// Unchecked cast from JsValue to this type.
52    ///
53    /// # Safety
54    /// This does not perform any runtime checks. The caller must ensure
55    /// the value is actually of the correct type.
56    fn unchecked_from_js(val: JsValue) -> Self;
57
58    /// Unchecked cast from a JsValue reference to a reference of this type.
59    ///
60    /// # Safety
61    /// This does not perform any runtime checks. The caller must ensure
62    /// the value is actually of the correct type.
63    fn unchecked_from_js_ref(val: &JsValue) -> &Self;
64
65    /// Try to cast this value to type T.
66    ///
67    /// Returns `Ok(T)` if the value is an instance of T,
68    /// otherwise returns `Err(self)` with the original value.
69    fn dyn_into<T>(self) -> Result<T, Self>
70    where
71        T: JsCast,
72    {
73        if self.has_type::<T>() {
74            Ok(self.unchecked_into())
75        } else {
76            Err(self)
77        }
78    }
79
80    /// Try to get a reference to type T from this value.
81    ///
82    /// Returns `Some(&T)` if the value is an instance of T,
83    /// otherwise returns `None`.
84    fn dyn_ref<T>(&self) -> Option<&T>
85    where
86        T: JsCast,
87    {
88        if self.has_type::<T>() {
89            Some(self.unchecked_ref())
90        } else {
91            None
92        }
93    }
94
95    /// Test whether this JS value is an instance of the type `T`.
96    ///
97    /// This method performs a dynamic check (at runtime) using the JS
98    /// `instanceof` operator. This method returns `self instanceof T`.
99    ///
100    /// Note that `instanceof` does not always work with primitive values or
101    /// across different realms (e.g. iframes). If you're not sure whether you
102    /// specifically need only `instanceof` it's recommended to use `has_type`
103    /// instead.
104    fn is_instance_of<T>(&self) -> bool
105    where
106        T: JsCast,
107    {
108        T::instanceof(self.as_ref())
109    }
110
111    /// Unchecked cast to another type.
112    fn unchecked_into<T>(self) -> T
113    where
114        T: JsCast,
115    {
116        T::unchecked_from_js(self.into())
117    }
118
119    /// Unchecked cast to a reference of another type.
120    fn unchecked_ref<T>(&self) -> &T
121    where
122        T: JsCast,
123    {
124        T::unchecked_from_js_ref(self.as_ref())
125    }
126}
127
128impl<T: JsCast> TryFromJsValue for T {
129    #[inline]
130    fn try_from_js_value(val: JsValue) -> Result<Self, JsValue> {
131        val.dyn_into()
132    }
133
134    #[inline]
135    fn try_from_js_value_ref(val: &JsValue) -> Option<Self> {
136        val.clone().dyn_into().ok()
137    }
138}
139
140/// Implement JsCast for JsValue itself (identity cast)
141impl JsCast for JsValue {
142    fn instanceof(_val: &JsValue) -> bool {
143        true // Everything is a JsValue
144    }
145
146    fn unchecked_from_js(val: JsValue) -> Self {
147        val
148    }
149
150    fn unchecked_from_js_ref(val: &JsValue) -> &Self {
151        val
152    }
153}
154
155impl AsRef<JsValue> for JsValue {
156    fn as_ref(&self) -> &JsValue {
157        self
158    }
159}