Skip to main content

rong_core/
value.rs

1use crate::{JSContext, JSContextImpl, JSResult, RongJSError};
2use std::fmt;
3use std::hash::Hash;
4
5mod convert;
6pub use convert::*;
7
8mod error;
9pub use error::*;
10
11mod exception;
12pub use exception::*;
13
14mod valuetype;
15pub use valuetype::{JSTypeOf, JSValueType};
16
17mod object;
18pub use object::*;
19
20mod array;
21pub use array::*;
22
23mod array_buffer;
24pub use array_buffer::*;
25
26mod bytes;
27pub(crate) use bytes::JSBytesData;
28pub use bytes::*;
29
30mod typed_array;
31pub use typed_array::*;
32
33mod function;
34pub use function::*;
35
36mod symbol;
37pub use symbol::*;
38
39mod date;
40pub use date::*;
41
42mod proxy;
43pub use proxy::*;
44
45pub trait JSValueImpl: Clone + PartialEq + Hash {
46    /// the JS engine specific type of JavaScript Value
47    type RawValue: Copy;
48
49    /// Associates with a type that implements JSContextImpl
50    /// This represents the context wrapper type (e.g. QJSContext)
51    type Context: JSContextImpl<Value = Self>;
52
53    /// Create a JSValue from borrowed raw parts, increasing reference count to ensure safety.
54    /// Used for values received from JS engine callbacks or external sources.
55    fn from_borrowed_raw(
56        ctx: <Self::Context as JSContextImpl>::RawContext,
57        value: Self::RawValue,
58    ) -> Self;
59
60    /// Create a JSValue from owned raw parts without reference counting.
61    /// Used for values newly created by Rust code that we own directly.
62    fn from_owned_raw(
63        ctx: <Self::Context as JSContextImpl>::RawContext,
64        value: Self::RawValue,
65    ) -> Self;
66
67    /// Consumes the ownship and returns the FFI level of JSValue but stop triggering drop.
68    /// This API should be used when engine API needs the ownshipe of JS variable
69    fn into_raw_value(self) -> Self::RawValue;
70
71    fn as_raw_value(&self) -> &Self::RawValue;
72    fn raw_value_for_api(&self) -> Self::RawValue {
73        *self.as_raw_value()
74    }
75    fn as_raw_context(&self) -> &<Self::Context as JSContextImpl>::RawContext;
76
77    /// Create JavaScript null value
78    fn create_null(ctx: &Self::Context) -> Self;
79
80    /// Create JavaScript undefined value
81    fn create_undefined(ctx: &Self::Context) -> Self;
82
83    /// Create a JavaScript Symbol with the given description
84    fn create_symbol(ctx: &Self::Context, descripiton: &str) -> Self;
85
86    /// Creates a JSValue by parsing a JSON string
87    fn from_json_str(ctx: &Self::Context, str: &str) -> Self;
88
89    /// Create a Date object from epoch milliseconds
90    fn create_date(ctx: &Self::Context, epoch_ms: f64) -> Self;
91}
92
93pub struct JSValue<V: JSValueImpl> {
94    inner: V,
95}
96
97impl<V: JSValueImpl> Clone for JSValue<V> {
98    fn clone(&self) -> Self {
99        Self {
100            inner: self.inner.clone(),
101        }
102    }
103}
104
105impl<V: JSValueImpl> PartialEq for JSValue<V> {
106    fn eq(&self, other: &Self) -> bool {
107        self.inner.eq(&other.inner)
108    }
109}
110
111impl<V: JSValueImpl> Eq for JSValue<V> {}
112
113impl<V: JSValueImpl> Hash for JSValue<V> {
114    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
115        self.inner.hash(state);
116    }
117}
118
119impl<V> JSValue<V>
120where
121    V: JSValueImpl,
122{
123    pub fn from_raw(_ctx: &JSContext<V::Context>, value: V) -> Self {
124        Self { inner: value }
125    }
126
127    pub fn as_value(&self) -> &V {
128        &self.inner
129    }
130
131    pub fn into_value(self) -> V {
132        self.inner
133    }
134
135    /// Returns the context associated with this value.
136    pub fn context(&self) -> JSContext<V::Context> {
137        JSContext::from_borrowed_raw_ptr(self.as_value().as_raw_context())
138    }
139
140    /// Converts a Rust value into a `JSValue`.
141    pub fn from_rust<T>(ctx: &JSContext<V::Context>, val: T) -> Self
142    where
143        T: IntoJSValue<V>,
144    {
145        <T as IntoJSValue<V>>::into_js_value(val, ctx)
146    }
147
148    /// Converts a JavaScript value into a Rust value.
149    pub fn to_rust<T>(self) -> JSResult<T>
150    where
151        T: FromJSValue<V>,
152    {
153        let ctx = self.context();
154        T::from_js_value(&ctx, self)
155    }
156
157    /// create JS UNDEFINED Value
158    pub fn undefined(ctx: &JSContext<V::Context>) -> Self {
159        let value = V::create_undefined(ctx.as_ref());
160        JSValue::from_raw(ctx, value)
161    }
162
163    /// create JS NULL Value
164    pub fn null(ctx: &JSContext<V::Context>) -> Self {
165        let value = V::create_null(ctx.as_ref());
166        JSValue::from_raw(ctx, value)
167    }
168}
169
170impl<V> JSValue<V>
171where
172    V: JSValueImpl + JSTypeOf,
173{
174    /// Convert JSValue into JSObject if it is an object
175    ///
176    /// # Returns
177    /// - `Some(JSObject)` if the value is an object
178    /// - `None` if the value is not an object
179    pub fn into_object(self) -> Option<JSObject<V>> {
180        self.take_is_object().map(|v| v.into())
181    }
182}
183
184impl<V> FromJSValue<V> for JSValue<V>
185where
186    V: JSValueImpl,
187{
188    fn from_js_value(_ctx: &JSContext<V::Context>, value: JSValue<V>) -> JSResult<Self> {
189        Ok(value)
190    }
191}
192
193impl<V> IntoJSValue<V> for JSValue<V>
194where
195    V: JSValueImpl,
196{
197    fn into_js_value(self, _ctx: &JSContext<V::Context>) -> JSValue<V> {
198        self
199    }
200}
201
202/// Converts a JSON string into a JSValue
203pub trait JsonToJSValue<V>
204where
205    V: JSValueImpl,
206{
207    /// Converts the JSON string into a JSValue using the provided context
208    fn json_to_js_value(self, ctx: &JSContext<V::Context>) -> JSResult<JSValue<V>>;
209}
210
211impl<V> JsonToJSValue<V> for &str
212where
213    V: JSObjectOps + JSTypeOf,
214{
215    fn json_to_js_value(self, ctx: &JSContext<V::Context>) -> JSResult<JSValue<V>> {
216        let result = V::from_json_str(ctx.as_ref(), self);
217        result.try_map(|v| JSValue::from_raw(ctx, v))
218    }
219}
220
221/// Provides safe conversion and mapping operations for JS values
222pub trait JSValueMapper<V: JSValueImpl> {
223    /// Attempts to convert the value using FromJSValue, returns error if it's an exception
224    fn try_convert<T>(self) -> JSResult<T>
225    where
226        T: FromJSValue<V>;
227
228    /// Maps the value using the provided closure if it's not an exception,
229    /// otherwise returns the error
230    fn try_map<T, F>(self, f: F) -> JSResult<T>
231    where
232        F: FnOnce(Self) -> T,
233        Self: Sized;
234}
235
236impl<V> JSValueMapper<V> for V
237where
238    V: JSTypeOf,
239    V: JSObjectOps,
240{
241    fn try_convert<T>(self) -> JSResult<T>
242    where
243        T: FromJSValue<V>,
244    {
245        self.try_map(|value| {
246            let ctx = JSContext::from_borrowed_raw_ptr(value.as_raw_context());
247            T::from_js_value(&ctx, JSValue::from_raw(&ctx, value))
248        })?
249    }
250
251    fn try_map<T, F>(self, f: F) -> JSResult<T>
252    where
253        F: FnOnce(Self) -> T,
254    {
255        if self.is_exception() {
256            let ctx: JSContext<V::Context> =
257                JSContext::from_borrowed_raw_ptr(self.as_raw_context());
258            Err(RongJSError::from_thrown_value(JSValue::from_raw(
259                &ctx, self,
260            )))
261        } else {
262            Ok(f(self))
263        }
264    }
265}
266
267#[macro_export]
268macro_rules! impl_js_converter {
269    ($target:ty, $in_type:ty, $out_type:ty, $create_fn:expr, $to_fn:expr) => {
270        impl TryInto<$out_type> for $target
271        where
272            Self: JSValueImpl,
273        {
274            type Error = RongJSError;
275            fn try_into(self) -> Result<$out_type, Self::Error> {
276                let mut result: $out_type = Default::default();
277                if unsafe {
278                    $to_fn(
279                        *self.as_raw_context(),
280                        self.raw_value_for_api(),
281                        &mut result,
282                    )
283                } < 0
284                {
285                    Err($crate::HostError::new(
286                        $crate::error::E_TYPE,
287                        format!(
288                            "Expected JSValue to be type {}, but got {:?}",
289                            std::any::type_name::<$out_type>(),
290                            self.type_of()
291                        ),
292                    )
293                    .with_name("TypeError")
294                    .into())
295                } else {
296                    Ok(result)
297                }
298            }
299        }
300
301        impl<T> From<(&T, $in_type)> for $target
302        where
303            T: JSContextImpl<RawContext = <$target as JSRawContext>::RawContext>,
304            $target: JSValueImpl<Context = T>,
305        {
306            fn from(t: (&T, $in_type)) -> Self {
307                let ctx = t.0.as_raw();
308                let raw = unsafe { $create_fn(*ctx, t.1) };
309                Self::from_owned_raw(*ctx, raw)
310            }
311        }
312    };
313
314    ($target:ty, $type:ty, $create_fn:expr, $to_fn:expr) => {
315        impl_js_converter!($target, $type, $type, $create_fn, $to_fn);
316    };
317}
318
319// blanket implementing.
320impl<V: JSValueImpl> crate::function::JSParameterType for JSValue<V> {}
321
322impl<V> fmt::Display for JSValue<V>
323where
324    V: JSTypeOf + JSValueConversion,
325{
326    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327        match self.type_of() {
328            JSValueType::Boolean => {
329                if let Ok(val) = self.clone().to_rust::<bool>() {
330                    write!(f, "{}", val)
331                } else {
332                    write!(f, "boolean")
333                }
334            }
335            JSValueType::Number => {
336                if let Ok(val) = self.clone().to_rust::<f64>() {
337                    write!(f, "{}", val)
338                } else {
339                    write!(f, "number")
340                }
341            }
342            JSValueType::String => {
343                if let Ok(val) = self.clone().to_rust::<String>() {
344                    write!(f, "{}", val)
345                } else {
346                    write!(f, "string")
347                }
348            }
349            JSValueType::Date => {
350                if let Ok(val) = self.clone().to_rust::<String>() {
351                    write!(f, "{}", val)
352                } else {
353                    write!(f, "Date")
354                }
355            }
356            JSValueType::Undefined => write!(f, "undefined"),
357            JSValueType::Null => write!(f, "null"),
358            JSValueType::BigInt => write!(f, "bigint"),
359            JSValueType::Object => write!(f, "object"),
360            JSValueType::Array => write!(f, "array"),
361            JSValueType::ArrayBuffer => write!(f, "arrayBuffer"),
362            JSValueType::Function => write!(f, "function"),
363            JSValueType::Constructor => write!(f, "constructor"),
364            JSValueType::Promise => write!(f, "promise"),
365            JSValueType::Symbol => write!(f, "symbol"),
366            JSValueType::Error => write!(f, "error"),
367            JSValueType::Exception => write!(f, "exception"),
368            JSValueType::Unknown => write!(f, "unknown"),
369        }
370    }
371}
372
373impl<V> fmt::Debug for JSValue<V>
374where
375    V: JSTypeOf + JSValueConversion,
376{
377    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
378        write!(f, "JSValue({})", self)
379    }
380}