1use std::collections::BTreeMap;
4
5use rquickjs::{
6 atom::PredefinedAtom,
7 function::{Constructor, IntoJsFunc},
8 object::Property,
9 prelude::Func,
10 Array, Coerced, Ctx, Error, Exception, FromJs, IntoAtom, IntoJs, Object, Result, Undefined,
11 Value,
12};
13
14use crate::utils::primordials::{BasePrimordials, Primordial};
15
16pub trait ObjectExt<'js> {
17 fn get_optional<K: IntoAtom<'js> + Clone, V: FromJs<'js>>(&self, k: K) -> Result<Option<V>>;
18 fn get_required<K: AsRef<str>, V: FromJs<'js>>(
19 &self,
20 k: K,
21 object_name: &'static str,
22 ) -> Result<V>;
23 fn into_object_or_throw(self, ctx: &Ctx<'js>, object_name: &'static str)
24 -> Result<Object<'js>>;
25}
26
27impl<'js> ObjectExt<'js> for Object<'js> {
28 fn get_optional<K: IntoAtom<'js> + Clone, V: FromJs<'js> + Sized>(
29 &self,
30 k: K,
31 ) -> Result<Option<V>> {
32 self.get::<K, Option<V>>(k)
33 }
34
35 fn get_required<K: AsRef<str>, V: FromJs<'js>>(
36 &self,
37 k: K,
38 object_name: &'static str,
39 ) -> Result<V> {
40 let k = k.as_ref();
41 self.get::<&str, Option<V>>(k)?.ok_or_else(|| {
42 Exception::throw_type(
43 self.ctx(),
44 &[object_name, " '", k, "' property required"].concat(),
45 )
46 })
47 }
48
49 fn into_object_or_throw(self, _: &Ctx<'js>, _: &'static str) -> Result<Object<'js>> {
50 Ok(self)
51 }
52}
53
54impl<'js> ObjectExt<'js> for Value<'js> {
55 fn get_optional<K: IntoAtom<'js> + Clone, V: FromJs<'js>>(&self, k: K) -> Result<Option<V>> {
56 if let Some(obj) = self.as_object() {
57 return obj.get_optional(k);
58 }
59 Ok(None)
60 }
61
62 fn get_required<K: AsRef<str>, V: FromJs<'js>>(
63 &self,
64 k: K,
65 object_name: &'static str,
66 ) -> Result<V> {
67 self.as_object()
68 .ok_or_else(|| not_a_object_error(self.ctx(), object_name))?
69 .get_required(k, object_name)
70 }
71
72 fn into_object_or_throw(
73 self,
74 ctx: &Ctx<'js>,
75 object_name: &'static str,
76 ) -> Result<Object<'js>> {
77 self.into_object()
78 .ok_or_else(|| not_a_object_error(ctx, object_name))
79 }
80}
81
82pub fn not_a_object_error(ctx: &Ctx<'_>, object_name: &str) -> Error {
83 Exception::throw_type(ctx, &[object_name, " is not an object"].concat())
84}
85
86pub struct Proxy<'js> {
87 target: Value<'js>,
88 options: Object<'js>,
89}
90
91impl<'js> IntoJs<'js> for Proxy<'js> {
92 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
93 BasePrimordials::get(ctx)?
94 .constructor_proxy
95 .construct::<_, Value>((self.target, self.options))
96 }
97}
98
99impl<'js> Proxy<'js> {
100 pub fn new(ctx: Ctx<'js>) -> Result<Self> {
101 let options = Object::new(ctx.clone())?;
102 Ok(Self {
103 target: Undefined.into_value(ctx),
104 options,
105 })
106 }
107
108 pub fn with_target(ctx: Ctx<'js>, target: Value<'js>) -> Result<Self> {
109 let options = Object::new(ctx)?;
110 Ok(Self { target, options })
111 }
112
113 pub fn setter<T, P: 'js>(&self, setter: Func<T, P>) -> Result<()>
114 where
115 T: IntoJsFunc<'js, P> + 'js,
116 {
117 self.options.set(PredefinedAtom::Setter, setter)?;
118 Ok(())
119 }
120
121 pub fn getter<T, P: 'js>(&self, getter: Func<T, P>) -> Result<()>
122 where
123 T: IntoJsFunc<'js, P> + 'js,
124 {
125 self.options.set(PredefinedAtom::Getter, getter)?;
126 Ok(())
127 }
128}
129
130pub fn array_to_btree_map<'js>(
131 ctx: &Ctx<'js>,
132 array: Array<'js>,
133) -> Result<BTreeMap<String, Coerced<String>>> {
134 let value = object_from_entries(ctx, array)?;
135 let value = value.into_value();
136 BTreeMap::from_js(ctx, value)
137}
138
139pub fn object_from_entries<'js>(ctx: &Ctx<'js>, array: Array<'js>) -> Result<Object<'js>> {
140 let obj = Object::new(ctx.clone())?;
141 for value in array.into_iter().flatten() {
142 if let Some(entry) = value.as_array() {
143 if let Ok(key) = entry.get::<Value>(0) {
144 if let Ok(value) = entry.get::<Value>(1) {
145 let _ = obj.set(key, value); }
147 }
148 }
149 }
150 Ok(obj)
151}
152
153pub fn define_subclass<'js, F, P>(
155 ctx: &Ctx<'js>,
156 name: &str,
157 parent: &Constructor<'js>,
158 construct: F,
159) -> Result<Constructor<'js>>
160where
161 F: IntoJsFunc<'js, P> + 'js,
162{
163 let parent_proto: Object = parent.get(PredefinedAtom::Prototype)?;
164 let proto = Object::new(ctx.clone())?;
165 proto.set_prototype(Some(&parent_proto))?;
166 let constructor = Constructor::new_prototype(ctx, proto, construct)?;
167 constructor.set_prototype(parent.as_object())?;
168 constructor.prop(PredefinedAtom::Name, Property::from(name).configurable())?;
169 Ok(constructor)
170}