Skip to main content

wry_bindgen/
sys.rs

1//! JavaScript system value wrappers and promise compatibility traits.
2
3use core::fmt;
4
5use crate::{
6    __rt::marker::ErasableGeneric, JsCast, JsError, JsGeneric, JsValue, convert::UpcastFrom,
7};
8use wry_bindgen_macro::wasm_bindgen;
9
10/// Marker trait for values that are either a resolution value or a promise-like value.
11pub trait Promising {
12    type Resolution;
13}
14
15#[wasm_bindgen(wasm_bindgen = crate)]
16extern "C" {
17    /// The JavaScript `undefined` value.
18    ///
19    /// This type represents the JavaScript `undefined` primitive value and can be
20    /// used as a generic type parameter to indicate that a value is `undefined`.
21    #[wasm_bindgen(is_type_of = JsValue::is_undefined, typescript_type = "undefined", no_upcast)]
22    #[derive(Clone, PartialEq)]
23    pub type Undefined;
24}
25
26impl Undefined {
27    /// The undefined constant.
28    pub const UNDEFINED: Undefined = Self {
29        obj: JsValue::UNDEFINED,
30    };
31}
32
33impl Eq for Undefined {}
34
35impl Default for Undefined {
36    fn default() -> Self {
37        Self::UNDEFINED
38    }
39}
40
41impl fmt::Debug for Undefined {
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        f.write_str("undefined")
44    }
45}
46
47impl fmt::Display for Undefined {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        f.write_str("undefined")
50    }
51}
52
53#[wasm_bindgen(wasm_bindgen = crate)]
54extern "C" {
55    /// The JavaScript `null` value.
56    ///
57    /// This type represents the JavaScript `null` primitive value and can be
58    /// used as a generic type parameter to indicate that a value is `null`.
59    #[wasm_bindgen(is_type_of = JsValue::is_null, typescript_type = "null", no_upcast)]
60    #[derive(Clone, PartialEq)]
61    pub type Null;
62}
63
64impl Null {
65    /// The null constant.
66    pub const NULL: Null = Self { obj: JsValue::NULL };
67}
68
69impl Eq for Null {}
70
71impl Default for Null {
72    fn default() -> Self {
73        Self::NULL
74    }
75}
76
77impl fmt::Debug for Null {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        f.write_str("null")
80    }
81}
82
83impl fmt::Display for Null {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        f.write_str("null")
86    }
87}
88
89impl UpcastFrom<Undefined> for Undefined {}
90impl UpcastFrom<()> for Undefined {}
91impl UpcastFrom<Undefined> for () {}
92impl UpcastFrom<Undefined> for JsValue {}
93impl UpcastFrom<Null> for Null {}
94impl UpcastFrom<Null> for JsValue {}
95impl UpcastFrom<()> for JsValue {}
96impl UpcastFrom<()> for () {}
97
98#[wasm_bindgen(wasm_bindgen = crate)]
99extern "C" {
100    /// A nullable JS value of type `T`.
101    ///
102    /// Unlike `Option<T>`, which is a Rust-side construct, `JsOption<T>` represents
103    /// a JS value that may be `T`, `null`, or `undefined`, where the null status is
104    /// not yet known in Rust. The value remains in JS until inspected via methods
105    /// like [`is_empty`](Self::is_empty), [`as_option`](Self::as_option), or
106    /// [`into_option`](Self::into_option).
107    ///
108    /// `T` must implement [`JsGeneric`], meaning it is any type that can be
109    /// represented as a `JsValue` (e.g., `JsString`, `Number`, `Object`, etc.).
110    /// `JsOption<T>` itself implements `JsGeneric`, so it can be used in all
111    /// generic positions that accept JS types.
112    #[wasm_bindgen(typescript_type = "any", no_upcast)]
113    #[derive(Clone, PartialEq)]
114    pub type JsOption<T = JsValue>;
115}
116
117impl<T: JsGeneric> JsOption<T> {
118    #[inline]
119    pub fn new() -> Self {
120        Undefined::UNDEFINED.unchecked_into()
121    }
122
123    #[inline]
124    pub fn wrap(val: T) -> Self {
125        val.unchecked_into()
126    }
127
128    #[inline]
129    pub fn from_option(opt: Option<T>) -> Self {
130        match opt {
131            Some(value) => Self::wrap(value),
132            None => Self::new(),
133        }
134    }
135
136    #[inline]
137    pub fn is_empty(&self) -> bool {
138        AsRef::<JsValue>::as_ref(self).is_undefined()
139    }
140
141    #[inline]
142    pub fn as_option(&self) -> Option<T> {
143        if self.is_empty() {
144            None
145        } else {
146            Some(T::unchecked_from_js(AsRef::<JsValue>::as_ref(self).clone()))
147        }
148    }
149
150    #[inline]
151    pub fn into_option(self) -> Option<T> {
152        if self.is_empty() {
153            None
154        } else {
155            Some(self.unchecked_into())
156        }
157    }
158
159    #[inline]
160    pub fn unwrap(self) -> T {
161        self.expect("called `JsOption::unwrap()` on an empty value")
162    }
163
164    #[inline]
165    pub fn expect(self, msg: &str) -> T {
166        match self.into_option() {
167            Some(value) => value,
168            None => panic!("{}", msg),
169        }
170    }
171
172    #[inline]
173    pub fn unwrap_or_default(self) -> T
174    where
175        T: Default,
176    {
177        self.into_option().unwrap_or_default()
178    }
179
180    #[inline]
181    pub fn unwrap_or_else<F>(self, f: F) -> T
182    where
183        F: FnOnce() -> T,
184    {
185        self.into_option().unwrap_or_else(f)
186    }
187}
188
189impl<T: JsGeneric> Default for JsOption<T> {
190    fn default() -> Self {
191        Self::new()
192    }
193}
194
195impl<T: JsGeneric + fmt::Debug> fmt::Debug for JsOption<T> {
196    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197        write!(f, "{}?(", core::any::type_name::<T>())?;
198        match self.as_option() {
199            Some(value) => write!(f, "{value:?}")?,
200            None => f.write_str("undefined")?,
201        }
202        f.write_str(")")
203    }
204}
205
206impl<T: JsGeneric + fmt::Display> fmt::Display for JsOption<T> {
207    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208        write!(f, "{}?(", core::any::type_name::<T>())?;
209        match self.as_option() {
210            Some(value) => write!(f, "{value}")?,
211            None => f.write_str("undefined")?,
212        }
213        f.write_str(")")
214    }
215}
216
217impl UpcastFrom<JsValue> for JsOption<JsValue> {}
218impl<T> UpcastFrom<Undefined> for JsOption<T> {}
219impl<T> UpcastFrom<()> for JsOption<T> {}
220impl<T> UpcastFrom<JsOption<T>> for JsValue {}
221impl<T, U> UpcastFrom<JsOption<U>> for JsOption<T> where T: UpcastFrom<U> {}
222
223impl Promising for JsValue {
224    type Resolution = JsValue;
225}
226
227impl Promising for () {
228    type Resolution = Undefined;
229}
230
231macro_rules! promising_self {
232        ($($ty:ty),* $(,)?) => {
233            $(
234                impl Promising for $ty {
235                    type Resolution = $ty;
236                }
237            )*
238        };
239    }
240
241promising_self!(
242    bool, char, f32, f64, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, JsError
243);
244
245impl Promising for alloc::string::String {
246    type Resolution = alloc::string::String;
247}
248
249impl<T: Promising> Promising for alloc::vec::Vec<T> {
250    type Resolution = alloc::vec::Vec<T::Resolution>;
251}
252
253impl<T: Promising> Promising for Option<T> {
254    type Resolution = Option<T::Resolution>;
255}
256
257impl<T: ErasableGeneric + Promising, E: ErasableGeneric> Promising for Result<T, E> {
258    type Resolution = Result<T::Resolution, E>;
259}