quickjs_rusty/value/
object.rs

1use std::fmt::Debug;
2use std::ops::Deref;
3
4use libquickjs_ng_sys as q;
5
6use crate::utils::make_cstring;
7use crate::{ExecutionError, ValueError};
8
9use super::OwnedJsValue;
10
11/// Wraps an object from the QuickJs runtime.
12/// Provides convenience property accessors.
13#[derive(Clone, Debug, PartialEq)]
14pub struct OwnedJsObject {
15    value: OwnedJsValue,
16}
17
18impl OwnedJsObject {
19    pub fn try_from_value(value: OwnedJsValue) -> Result<Self, ValueError> {
20        if !value.is_object() {
21            Err(ValueError::Internal("Expected an object".to_string()))
22        } else {
23            Ok(Self { value })
24        }
25    }
26
27    pub fn into_value(self) -> OwnedJsValue {
28        self.value
29    }
30
31    pub fn properties_iter(&self) -> Result<OwnedJsPropertyIterator, ValueError> {
32        let prop_iter = OwnedJsPropertyIterator::from_object(self.value.context(), self.clone())?;
33
34        Ok(prop_iter)
35    }
36
37    pub fn property(&self, name: &str) -> Result<Option<OwnedJsValue>, ExecutionError> {
38        // TODO: prevent allocation
39        let cname = make_cstring(name)?;
40        let value = {
41            let raw = unsafe {
42                q::JS_GetPropertyStr(self.value.context(), self.value.value, cname.as_ptr())
43            };
44            OwnedJsValue::new(self.value.context(), raw)
45        };
46        let tag = value.tag();
47
48        if tag.is_exception() {
49            Err(ExecutionError::Internal(format!(
50                "Exception while getting property '{}'",
51                name
52            )))
53        }
54        //  else if tag.is_undefined() {
55        //     Ok(None)
56        // }
57        else {
58            Ok(Some(value))
59        }
60    }
61
62    pub fn property_require(&self, name: &str) -> Result<OwnedJsValue, ExecutionError> {
63        self.property(name)?
64            .ok_or_else(|| ExecutionError::Internal(format!("Property '{}' not found", name)))
65    }
66
67    /// Determine if the object is a promise by checking the presence of
68    /// a 'then' and a 'catch' property.
69    pub fn is_promise(&self) -> Result<bool, ExecutionError> {
70        if let Some(p) = self.property("then")? {
71            if p.is_function() {
72                return Ok(true);
73            }
74        }
75        if let Some(p) = self.property("catch")? {
76            if p.is_function() {
77                return Ok(true);
78            }
79        }
80        Ok(false)
81    }
82
83    pub fn set_property(&self, name: &str, value: OwnedJsValue) -> Result<(), ExecutionError> {
84        let cname = make_cstring(name)?;
85        unsafe {
86            // NOTE: SetPropertyStr takes ownership of the value.
87            // We do not, however, call OwnedJsValue::extract immediately, so
88            // the inner JSValue is still managed.
89            // `mem::forget` is called below only if SetProperty succeeds.
90            // This prevents leaks when an error occurs.
91            let ret = q::JS_SetPropertyStr(
92                self.value.context(),
93                self.value.value,
94                cname.as_ptr(),
95                value.value,
96            );
97
98            if ret < 0 {
99                Err(ExecutionError::Internal(
100                    "Could not set property".to_string(),
101                ))
102            } else {
103                // Now we can call forget to prevent calling the destructor.
104                std::mem::forget(value);
105                Ok(())
106            }
107        }
108    }
109}
110
111impl Deref for OwnedJsObject {
112    type Target = OwnedJsValue;
113
114    fn deref(&self) -> &Self::Target {
115        &self.value
116    }
117}
118
119#[derive(Clone, Debug, PartialEq)]
120pub struct OwnedJsPropertyIterator {
121    context: *mut q::JSContext,
122    object: OwnedJsObject,
123    properties: *mut q::JSPropertyEnum,
124    length: u32,
125    cur_index: u32,
126}
127
128impl OwnedJsPropertyIterator {
129    pub fn from_object(
130        context: *mut q::JSContext,
131        object: OwnedJsObject,
132    ) -> Result<Self, ValueError> {
133        let mut properties: *mut q::JSPropertyEnum = std::ptr::null_mut();
134        let mut length: u32 = 0;
135
136        let flags = (q::JS_GPN_STRING_MASK | q::JS_GPN_SYMBOL_MASK | q::JS_GPN_ENUM_ONLY) as i32;
137        let ret = unsafe {
138            q::JS_GetOwnPropertyNames(
139                context,
140                &mut properties,
141                &mut length,
142                object.value.value,
143                flags,
144            )
145        };
146        if ret != 0 {
147            return Err(ValueError::Internal(
148                "Could not get object properties".into(),
149            ));
150        }
151
152        Ok(Self {
153            context,
154            object,
155            properties,
156            length,
157            cur_index: 0,
158        })
159    }
160}
161
162/// Iterator over the properties of an object.
163/// The iterator yields key first and then value.
164impl Iterator for OwnedJsPropertyIterator {
165    type Item = Result<OwnedJsValue, ExecutionError>;
166
167    fn next(&mut self) -> Option<Self::Item> {
168        let cur_index = self.cur_index / 2;
169        let is_key = (self.cur_index % 2) == 0;
170
171        if cur_index >= self.length {
172            return None;
173        }
174
175        let prop = unsafe { self.properties.offset(cur_index as isize) };
176
177        let value = if is_key {
178            let pair_key = unsafe { q::JS_AtomToString(self.context, (*prop).atom) };
179            let tag = unsafe { q::JS_Ext_ValueGetTag(pair_key) };
180            if tag == q::JS_TAG_EXCEPTION {
181                return Some(Err(ExecutionError::Internal(
182                    "Could not get object property name".into(),
183                )));
184            }
185
186            OwnedJsValue::new(self.context, pair_key)
187        } else {
188            let pair_value =
189                unsafe { q::JS_GetProperty(self.context, self.object.value.value, (*prop).atom) };
190            let tag = unsafe { q::JS_Ext_ValueGetTag(pair_value) };
191            if tag == q::JS_TAG_EXCEPTION {
192                return Some(Err(ExecutionError::Internal(
193                    "Could not get object property".into(),
194                )));
195            }
196
197            OwnedJsValue::new(self.context, pair_value)
198        };
199
200        self.cur_index += 1;
201
202        Some(Ok(value))
203    }
204}
205
206impl Drop for OwnedJsPropertyIterator {
207    fn drop(&mut self) {
208        unsafe {
209            q::JS_FreePropertyEnum(self.context, self.properties, self.length);
210        }
211    }
212}