1use crate::{
4 convert::FromIteratorJs, qjs, Array, Atom, Ctx, FromAtom, FromJs, IntoAtom, IntoJs, Result,
5 Value,
6};
7use std::{iter::FusedIterator, marker::PhantomData, mem};
8
9mod property;
10pub use property::{Accessor, AsProperty, Property, PropertyFlags};
11
12#[derive(Debug, PartialEq, Clone, Hash, Eq)]
14#[repr(transparent)]
15pub struct Object<'js>(pub(crate) Value<'js>);
16
17impl<'js> Object<'js> {
18 pub fn new(ctx: Ctx<'js>) -> Result<Self> {
20 Ok(unsafe {
21 let val = qjs::JS_NewObject(ctx.as_ptr());
22 let val = ctx.handle_exception(val)?;
23 Object::from_js_value(ctx, val)
24 })
25 }
26
27 pub fn get<K: IntoAtom<'js>, V: FromJs<'js>>(&self, k: K) -> Result<V> {
29 let atom = k.into_atom(self.ctx())?;
30 V::from_js(self.ctx(), unsafe {
31 let val = qjs::JS_GetProperty(self.0.ctx.as_ptr(), self.0.as_js_value(), atom.atom);
32 let val = self.0.ctx.handle_exception(val)?;
33 Value::from_js_value(self.0.ctx.clone(), val)
34 })
35 }
36
37 pub fn contains_key<K>(&self, k: K) -> Result<bool>
39 where
40 K: IntoAtom<'js>,
41 {
42 let atom = k.into_atom(self.ctx())?;
43 unsafe {
44 let res = qjs::JS_HasProperty(self.0.ctx.as_ptr(), self.0.as_js_value(), atom.atom);
45 if res < 0 {
46 return Err(self.0.ctx.raise_exception());
47 }
48 Ok(res == 1)
49 }
50 }
51
52 pub fn set<K: IntoAtom<'js>, V: IntoJs<'js>>(&self, key: K, value: V) -> Result<()> {
54 let atom = key.into_atom(self.ctx())?;
55 let val = value.into_js(self.ctx())?;
56 unsafe {
57 if qjs::JS_SetProperty(
58 self.0.ctx.as_ptr(),
59 self.0.as_js_value(),
60 atom.atom,
61 val.into_js_value(),
62 ) < 0
63 {
64 return Err(self.0.ctx.raise_exception());
65 }
66 }
67 Ok(())
68 }
69
70 pub fn remove<K: IntoAtom<'js>>(&self, key: K) -> Result<()> {
72 let atom = key.into_atom(self.ctx())?;
73 unsafe {
74 if qjs::JS_DeleteProperty(
75 self.0.ctx.as_ptr(),
76 self.0.as_js_value(),
77 atom.atom,
78 qjs::JS_PROP_THROW as _,
79 ) < 0
80 {
81 return Err(self.0.ctx.raise_exception());
82 }
83 }
84 Ok(())
85 }
86
87 pub fn is_empty(&self) -> bool {
89 self.keys::<Atom>().next().is_none()
90 }
91
92 pub fn len(&self) -> usize {
94 self.keys::<Atom>().count()
95 }
96
97 pub fn keys<K: FromAtom<'js>>(&self) -> ObjectKeysIter<'js, K> {
99 self.own_keys(Filter::default())
100 }
101
102 pub fn own_keys<K: FromAtom<'js>>(&self, filter: Filter) -> ObjectKeysIter<'js, K> {
104 ObjectKeysIter {
105 state: Some(IterState::new(&self.0, filter.flags)),
106 marker: PhantomData,
107 }
108 }
109
110 pub fn props<K: FromAtom<'js>, V: FromJs<'js>>(&self) -> ObjectIter<'js, K, V> {
112 self.own_props(Filter::default())
113 }
114
115 pub fn own_props<K: FromAtom<'js>, V: FromJs<'js>>(
117 &self,
118 filter: Filter,
119 ) -> ObjectIter<'js, K, V> {
120 ObjectIter {
121 state: Some(IterState::new(&self.0, filter.flags)),
122 object: self.clone(),
123 marker: PhantomData,
124 }
125 }
126
127 pub fn values<K: FromAtom<'js>>(&self) -> ObjectValuesIter<'js, K> {
129 self.own_values(Filter::default())
130 }
131
132 pub fn own_values<K: FromAtom<'js>>(&self, filter: Filter) -> ObjectValuesIter<'js, K> {
134 ObjectValuesIter {
135 state: Some(IterState::new(&self.0, filter.flags)),
136 object: self.clone(),
137 marker: PhantomData,
138 }
139 }
140
141 pub fn get_prototype(&self) -> Option<Object<'js>> {
145 unsafe {
146 let proto = qjs::JS_GetPrototype(self.0.ctx.as_ptr(), self.0.as_js_value());
147 if qjs::JS_IsNull(proto) {
148 None
149 } else {
150 Some(Object::from_js_value(self.0.ctx.clone(), proto))
151 }
152 }
153 }
154
155 pub fn set_prototype(&self, proto: Option<&Object<'js>>) -> Result<()> {
161 let proto = proto.map(|x| x.as_js_value()).unwrap_or(qjs::JS_NULL);
162 unsafe {
163 if 1 != qjs::JS_SetPrototype(self.0.ctx.as_ptr(), self.0.as_js_value(), proto) {
164 Err(self.0.ctx.raise_exception())
165 } else {
166 Ok(())
167 }
168 }
169 }
170
171 pub fn is_instance_of(&self, class: impl AsRef<Value<'js>>) -> bool {
173 let class = class.as_ref();
174 0 != unsafe {
175 qjs::JS_IsInstanceOf(
176 self.0.ctx.as_ptr(),
177 self.0.as_js_value(),
178 class.as_js_value(),
179 )
180 }
181 }
182
183 pub fn into_array(self) -> Option<Array<'js>> {
185 if self.is_array() {
186 Some(Array(self))
187 } else {
188 None
189 }
190 }
191}
192
193#[derive(Debug, Clone, Copy)]
195#[repr(transparent)]
196pub struct Filter {
197 flags: qjs::c_int,
198}
199
200impl Default for Filter {
202 fn default() -> Self {
203 Self::new().string().enum_only()
204 }
205}
206
207impl Filter {
208 pub fn new() -> Self {
210 Self { flags: 0 }
211 }
212
213 #[must_use]
215 pub fn string(mut self) -> Self {
216 self.flags |= qjs::JS_GPN_STRING_MASK as qjs::c_int;
217 self
218 }
219
220 #[must_use]
222 pub fn symbol(mut self) -> Self {
223 self.flags |= qjs::JS_GPN_SYMBOL_MASK as qjs::c_int;
224 self
225 }
226
227 #[must_use]
229 pub fn private(mut self) -> Self {
230 self.flags |= qjs::JS_GPN_PRIVATE_MASK as qjs::c_int;
231 self
232 }
233
234 #[must_use]
236 pub fn enum_only(mut self) -> Self {
237 self.flags |= qjs::JS_GPN_ENUM_ONLY as qjs::c_int;
238 self
239 }
240}
241
242struct IterState<'js> {
243 ctx: Ctx<'js>,
244 enums: *mut qjs::JSPropertyEnum,
245 index: u32,
246 count: u32,
247}
248
249impl<'js> IterState<'js> {
250 fn new(obj: &Value<'js>, flags: qjs::c_int) -> Result<Self> {
251 let ctx = obj.ctx();
252
253 let mut enums = mem::MaybeUninit::uninit();
254 let mut count = mem::MaybeUninit::uninit();
255
256 let (enums, count) = unsafe {
257 if qjs::JS_GetOwnPropertyNames(
258 ctx.as_ptr(),
259 enums.as_mut_ptr(),
260 count.as_mut_ptr(),
261 obj.value,
262 flags,
263 ) < 0
264 {
265 return Err(ctx.raise_exception());
266 }
267 let enums = enums.assume_init();
268 let count = count.assume_init();
269 (enums, count)
270 };
271
272 Ok(Self {
273 ctx: ctx.clone(),
274 enums,
275 count,
276 index: 0,
277 })
278 }
279}
280
281impl<'js> Drop for IterState<'js> {
282 fn drop(&mut self) {
283 for index in self.index..self.count {
285 let elem = unsafe { &*self.enums.offset(index as isize) };
286 unsafe { qjs::JS_FreeAtom(self.ctx.as_ptr(), elem.atom) };
287 }
288
289 unsafe { qjs::js_free(self.ctx.as_ptr(), self.enums as _) };
291 }
292}
293
294impl<'js> Iterator for IterState<'js> {
295 type Item = Atom<'js>;
296
297 fn next(&mut self) -> Option<Self::Item> {
298 if self.index < self.count {
299 let elem = unsafe { &*self.enums.offset(self.index as _) };
300 self.index += 1;
301 let atom = unsafe { Atom::from_atom_val(self.ctx.clone(), elem.atom) };
302 Some(atom)
303 } else {
304 None
305 }
306 }
307
308 fn size_hint(&self) -> (usize, Option<usize>) {
309 let len = self.len();
310 (len, Some(len))
311 }
312}
313
314impl<'js> DoubleEndedIterator for IterState<'js> {
315 fn next_back(&mut self) -> Option<Self::Item> {
316 if self.index < self.count {
317 self.count -= 1;
318 let elem = unsafe { &*self.enums.offset(self.count as _) };
319 let atom = unsafe { Atom::from_atom_val(self.ctx.clone(), elem.atom) };
320 Some(atom)
321 } else {
322 None
323 }
324 }
325}
326
327impl<'js> ExactSizeIterator for IterState<'js> {
328 fn len(&self) -> usize {
329 (self.count - self.index) as _
330 }
331}
332
333impl<'js> FusedIterator for IterState<'js> {}
334
335pub struct ObjectKeysIter<'js, K> {
337 state: Option<Result<IterState<'js>>>,
338 marker: PhantomData<K>,
339}
340
341impl<'js, K> Iterator for ObjectKeysIter<'js, K>
342where
343 K: FromAtom<'js>,
344{
345 type Item = Result<K>;
346
347 fn next(&mut self) -> Option<Self::Item> {
348 if let Some(Ok(state)) = &mut self.state {
349 match state.next() {
350 Some(atom) => Some(K::from_atom(atom)),
351 None => {
352 self.state = None;
353 None
354 }
355 }
356 } else if self.state.is_none() {
357 None
358 } else if let Some(Err(error)) = self.state.take() {
359 Some(Err(error))
360 } else {
361 unreachable!();
362 }
363 }
364
365 fn size_hint(&self) -> (usize, Option<usize>) {
366 let len = self.len();
367 (len, Some(len))
368 }
369}
370
371impl<'js, K> DoubleEndedIterator for ObjectKeysIter<'js, K>
372where
373 K: FromAtom<'js>,
374{
375 fn next_back(&mut self) -> Option<Self::Item> {
376 if let Some(Ok(state)) = &mut self.state {
377 match state.next_back() {
378 Some(atom) => Some(K::from_atom(atom)),
379 None => {
380 self.state = None;
381 None
382 }
383 }
384 } else if self.state.is_none() {
385 None
386 } else if let Some(Err(error)) = self.state.take() {
387 Some(Err(error))
388 } else {
389 unreachable!();
390 }
391 }
392}
393
394impl<'js, K> ExactSizeIterator for ObjectKeysIter<'js, K>
395where
396 K: FromAtom<'js>,
397{
398 fn len(&self) -> usize {
399 if let Some(Ok(state)) = &self.state {
400 state.len()
401 } else {
402 0
403 }
404 }
405}
406
407impl<'js, K> FusedIterator for ObjectKeysIter<'js, K> where K: FromAtom<'js> {}
408
409pub struct ObjectIter<'js, K, V> {
411 state: Option<Result<IterState<'js>>>,
412 object: Object<'js>,
413 marker: PhantomData<(K, V)>,
414}
415
416impl<'js, K, V> Iterator for ObjectIter<'js, K, V>
417where
418 K: FromAtom<'js>,
419 V: FromJs<'js>,
420{
421 type Item = Result<(K, V)>;
422
423 fn next(&mut self) -> Option<Self::Item> {
424 if let Some(Ok(state)) = &mut self.state {
425 match state.next() {
426 Some(atom) => Some(
427 K::from_atom(atom.clone())
428 .and_then(|key| self.object.get(atom).map(|val| (key, val))),
429 ),
430 None => {
431 self.state = None;
432 None
433 }
434 }
435 } else if self.state.is_none() {
436 None
437 } else if let Some(Err(error)) = self.state.take() {
438 Some(Err(error))
439 } else {
440 unreachable!();
441 }
442 }
443
444 fn size_hint(&self) -> (usize, Option<usize>) {
445 let len = self.len();
446 (len, Some(len))
447 }
448}
449
450impl<'js, K, V> DoubleEndedIterator for ObjectIter<'js, K, V>
451where
452 K: FromAtom<'js>,
453 V: FromJs<'js>,
454{
455 fn next_back(&mut self) -> Option<Self::Item> {
456 if let Some(Ok(state)) = &mut self.state {
457 match state.next_back() {
458 Some(atom) => Some(
459 K::from_atom(atom.clone())
460 .and_then(|key| self.object.get(atom).map(|val| (key, val))),
461 ),
462 None => {
463 self.state = None;
464 None
465 }
466 }
467 } else if self.state.is_none() {
468 None
469 } else if let Some(Err(error)) = self.state.take() {
470 Some(Err(error))
471 } else {
472 unreachable!();
473 }
474 }
475}
476
477impl<'js, K, V> ExactSizeIterator for ObjectIter<'js, K, V>
478where
479 K: FromAtom<'js>,
480 V: FromJs<'js>,
481{
482 fn len(&self) -> usize {
483 if let Some(Ok(state)) = &self.state {
484 state.len()
485 } else {
486 0
487 }
488 }
489}
490
491impl<'js, K, V> FusedIterator for ObjectIter<'js, K, V>
492where
493 K: FromAtom<'js>,
494 V: FromJs<'js>,
495{
496}
497
498pub struct ObjectValuesIter<'js, V> {
500 state: Option<Result<IterState<'js>>>,
501 object: Object<'js>,
502 marker: PhantomData<V>,
503}
504
505impl<'js, V> Iterator for ObjectValuesIter<'js, V>
506where
507 V: FromJs<'js>,
508{
509 type Item = Result<V>;
510
511 fn next(&mut self) -> Option<Self::Item> {
512 if let Some(Ok(state)) = &mut self.state {
513 match state.next() {
514 Some(atom) => Some(self.object.get(atom)),
515 None => {
516 self.state = None;
517 None
518 }
519 }
520 } else if self.state.is_none() {
521 None
522 } else if let Some(Err(error)) = self.state.take() {
523 Some(Err(error))
524 } else {
525 unreachable!();
526 }
527 }
528
529 fn size_hint(&self) -> (usize, Option<usize>) {
530 let len = self.len();
531 (len, Some(len))
532 }
533}
534
535impl<'js, V> DoubleEndedIterator for ObjectValuesIter<'js, V>
536where
537 V: FromJs<'js>,
538{
539 fn next_back(&mut self) -> Option<Self::Item> {
540 if let Some(Ok(state)) = &mut self.state {
541 match state.next_back() {
542 Some(atom) => Some(self.object.get(atom)),
543 None => {
544 self.state = None;
545 None
546 }
547 }
548 } else if self.state.is_none() {
549 None
550 } else if let Some(Err(error)) = self.state.take() {
551 Some(Err(error))
552 } else {
553 unreachable!();
554 }
555 }
556}
557
558impl<'js, V> ExactSizeIterator for ObjectValuesIter<'js, V>
559where
560 V: FromJs<'js>,
561{
562 fn len(&self) -> usize {
563 if let Some(Ok(state)) = &self.state {
564 state.len()
565 } else {
566 0
567 }
568 }
569}
570
571impl<'js, V> FusedIterator for ObjectValuesIter<'js, V> where V: FromJs<'js> {}
572
573impl<'js> IntoIterator for Object<'js> {
574 type Item = Result<(Atom<'js>, Value<'js>)>;
575 type IntoIter = ObjectIter<'js, Atom<'js>, Value<'js>>;
576
577 fn into_iter(self) -> Self::IntoIter {
578 let flags = qjs::JS_GPN_STRING_MASK as _;
579 ObjectIter {
580 state: Some(IterState::new(&self.0, flags)),
581 object: self,
582 marker: PhantomData,
583 }
584 }
585}
586
587impl<'js, K, V> FromIteratorJs<'js, (K, V)> for Object<'js>
588where
589 K: IntoAtom<'js>,
590 V: IntoJs<'js>,
591{
592 type Item = (Atom<'js>, Value<'js>);
593
594 fn from_iter_js<T>(ctx: &Ctx<'js>, iter: T) -> Result<Self>
595 where
596 T: IntoIterator<Item = (K, V)>,
597 {
598 let object = Object::new(ctx.clone())?;
599 for (key, value) in iter {
600 let key = key.into_atom(ctx)?;
601 let value = value.into_js(ctx)?;
602 object.set(key, value)?;
603 }
604 Ok(object)
605 }
606}
607
608#[cfg(test)]
609mod test {
610 use crate::*;
611
612 #[test]
613 fn from_javascript() {
614 test_with(|ctx| {
615 let val: Object = ctx
616 .eval(
617 r#"
618 let obj = {};
619 obj['a'] = 3;
620 obj[3] = 'a';
621 obj
622 "#,
623 )
624 .unwrap();
625
626 let text: StdString = val.get(3).unwrap();
627 assert_eq!(text, "a");
628 let int: i32 = val.get("a").unwrap();
629 assert_eq!(int, 3);
630 let int: StdString = val.get(3).unwrap();
631 assert_eq!(int, "a");
632 val.set("hallo", "foo").unwrap();
633 let text: StdString = val.get("hallo").unwrap();
634 assert_eq!(text, "foo".to_string());
635 val.remove("hallo").unwrap();
636 let text: Option<StdString> = val.get("hallo").unwrap();
637 assert_eq!(text, None);
638 });
639 }
640
641 #[test]
642 fn types() {
643 test_with(|ctx| {
644 let val: Object = ctx
645 .eval(
646 r#"
647 let array_3 = [];
648 array_3[3] = "foo";
649 array_3[99] = 4;
650 ({
651 array_1: [0,1,2,3,4,5],
652 array_2: [0,"foo",{},undefined,4,5],
653 array_3: array_3,
654 func_1: () => 1,
655 func_2: function(){ return "foo"},
656 obj_1: {
657 a: 1,
658 b: "foo",
659 },
660 })
661 "#,
662 )
663 .unwrap();
664 assert!(val.get::<_, Object>("array_1").unwrap().is_array());
665 assert!(val.get::<_, Object>("array_2").unwrap().is_array());
666 assert!(val.get::<_, Object>("array_3").unwrap().is_array());
667 assert!(val.get::<_, Object>("func_1").unwrap().is_function());
668 assert!(val.get::<_, Object>("func_2").unwrap().is_function());
669 assert!(!val.get::<_, Object>("obj_1").unwrap().is_function());
670 assert!(!val.get::<_, Object>("obj_1").unwrap().is_array());
671 })
672 }
673
674 #[test]
675 fn own_keys_iter() {
676 test_with(|ctx| {
677 let val: Object = ctx
678 .eval(
679 r#"
680 ({
681 123: 123,
682 str: "abc",
683 arr: [],
684 '': undefined,
685 })
686 "#,
687 )
688 .unwrap();
689 let keys = val.keys().collect::<Result<Vec<StdString>>>().unwrap();
690 assert_eq!(keys.len(), 4);
691 assert_eq!(keys[0], "123");
692 assert_eq!(keys[1], "str");
693 assert_eq!(keys[2], "arr");
694 assert_eq!(keys[3], "");
695 })
696 }
697
698 #[test]
699 fn own_props_iter() {
700 test_with(|ctx| {
701 let val: Object = ctx
702 .eval(
703 r#"
704 ({
705 123: "",
706 str: "abc",
707 '': "def",
708 })
709 "#,
710 )
711 .unwrap();
712 let pairs = val
713 .props()
714 .collect::<Result<Vec<(StdString, StdString)>>>()
715 .unwrap();
716 assert_eq!(pairs.len(), 3);
717 assert_eq!(pairs[0].0, "123");
718 assert_eq!(pairs[0].1, "");
719 assert_eq!(pairs[1].0, "str");
720 assert_eq!(pairs[1].1, "abc");
721 assert_eq!(pairs[2].0, "");
722 assert_eq!(pairs[2].1, "def");
723 })
724 }
725
726 #[test]
727 fn into_iter() {
728 test_with(|ctx| {
729 let val: Object = ctx
730 .eval(
731 r#"
732 ({
733 123: 123,
734 str: "abc",
735 arr: [],
736 '': undefined,
737 })
738 "#,
739 )
740 .unwrap();
741 let pairs = val.into_iter().collect::<Result<Vec<_>>>().unwrap();
742 assert_eq!(pairs.len(), 4);
743 assert_eq!(pairs[0].0.clone().to_string().unwrap(), "123");
744 assert_eq!(i32::from_js(&ctx, pairs[0].1.clone()).unwrap(), 123);
745 assert_eq!(pairs[1].0.clone().to_string().unwrap(), "str");
746 assert_eq!(StdString::from_js(&ctx, pairs[1].1.clone()).unwrap(), "abc");
747 assert_eq!(pairs[2].0.clone().to_string().unwrap(), "arr");
748 assert_eq!(Array::from_js(&ctx, pairs[2].1.clone()).unwrap().len(), 0);
749 assert_eq!(pairs[3].0.clone().to_string().unwrap(), "");
750 assert_eq!(
751 Undefined::from_js(&ctx, pairs[3].1.clone()).unwrap(),
752 Undefined
753 );
754 })
755 }
756
757 #[test]
758 fn iter_take() {
759 test_with(|ctx| {
760 let val: Object = ctx
761 .eval(
762 r#"
763 ({
764 123: 123,
765 str: "abc",
766 arr: [],
767 '': undefined,
768 })
769 "#,
770 )
771 .unwrap();
772 let keys = val
773 .keys()
774 .take(1)
775 .collect::<Result<Vec<StdString>>>()
776 .unwrap();
777 assert_eq!(keys.len(), 1);
778 assert_eq!(keys[0], "123");
779 })
780 }
781
782 #[test]
783 fn collect_js() {
784 test_with(|ctx| {
785 let object = [("a", "bc"), ("$_", ""), ("", "xyz")]
786 .iter()
787 .cloned()
788 .collect_js::<Object>(&ctx)
789 .unwrap();
790 assert_eq!(
791 StdString::from_js(&ctx, object.get("a").unwrap()).unwrap(),
792 "bc"
793 );
794 assert_eq!(
795 StdString::from_js(&ctx, object.get("$_").unwrap()).unwrap(),
796 ""
797 );
798 assert_eq!(
799 StdString::from_js(&ctx, object.get("").unwrap()).unwrap(),
800 "xyz"
801 );
802 })
803 }
804}