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}