1use crate::{
2 function::IntoJsFunc, qjs, Ctx, Function, IntoAtom, IntoJs, Object, Result, Undefined, Value,
3};
4
5impl<'js> Object<'js> {
6 pub fn prop<K, V, P>(&self, key: K, prop: V) -> Result<()>
30 where
31 K: IntoAtom<'js>,
32 V: AsProperty<'js, P>,
33 {
34 let ctx = self.ctx();
35 let key = key.into_atom(ctx)?;
36 let (flags, value, getter, setter) = prop.config(ctx)?;
37 let flags = flags | (qjs::JS_PROP_THROW as PropertyFlags);
38 unsafe {
39 let res = qjs::JS_DefineProperty(
40 ctx.as_ptr(),
41 self.0.as_js_value(),
42 key.atom,
43 value.as_js_value(),
44 getter.as_js_value(),
45 setter.as_js_value(),
46 flags,
47 );
48 if res < 0 {
49 return Err(self.0.ctx.raise_exception());
50 }
51 }
52 Ok(())
53 }
54}
55
56pub type PropertyFlags = qjs::c_int;
57
58pub trait AsProperty<'js, P> {
60 fn config(self, ctx: &Ctx<'js>) -> Result<(PropertyFlags, Value<'js>, Value<'js>, Value<'js>)>;
68}
69
70macro_rules! wrapper_impls {
71 ($($(#[$type_meta:meta])* $type:ident<$($param:ident),*>($($field:ident)*; $($flag:ident)*))*) => {
72 $(
73 $(#[$type_meta])*
74 #[derive(Debug, Clone, Copy)]
75 pub struct $type<$($param),*> {
76 flags: PropertyFlags,
77 $($field: $param,)*
78 }
79
80 impl<$($param),*> $type<$($param),*> {
81 $(wrapper_impls!{@flag $flag concat!("Make the property to be ", stringify!($flag))})*
82 }
83 )*
84 };
85
86 (@flag $flag:ident $doc:expr) => {
87 #[doc = $doc]
88 #[must_use]
89 pub fn $flag(mut self) -> Self {
90 self.flags |= wrapper_impls!(@flag $flag);
91 self
92 }
93 };
94
95 (@flag $flag:ident) => { wrapper_impls!{@_flag $flag} as PropertyFlags };
96 (@_flag configurable) => { qjs::JS_PROP_CONFIGURABLE };
97 (@_flag enumerable) => { qjs::JS_PROP_ENUMERABLE };
98 (@_flag writable) => { qjs::JS_PROP_WRITABLE };
99 (@_flag value) => { qjs::JS_PROP_HAS_VALUE };
100 (@_flag get) => { qjs::JS_PROP_HAS_GET };
101 (@_flag set) => { qjs::JS_PROP_HAS_SET };
102}
103
104impl<'js, T> AsProperty<'js, T> for T
105where
106 T: IntoJs<'js>,
107{
108 fn config(self, ctx: &Ctx<'js>) -> Result<(PropertyFlags, Value<'js>, Value<'js>, Value<'js>)> {
109 Ok((
110 wrapper_impls!(@flag value),
111 self.into_js(ctx)?,
112 Undefined.into_js(ctx)?,
113 Undefined.into_js(ctx)?,
114 ))
115 }
116}
117
118wrapper_impls! {
119 Property<T>(value; writable configurable enumerable)
121 Accessor<G, S>(get set; configurable enumerable)
123}
124
125impl<T> From<T> for Property<T> {
127 fn from(value: T) -> Self {
128 Self {
129 flags: wrapper_impls!(@flag value),
130 value,
131 }
132 }
133}
134
135impl<'js, T> AsProperty<'js, T> for Property<T>
136where
137 T: IntoJs<'js>,
138{
139 fn config(self, ctx: &Ctx<'js>) -> Result<(PropertyFlags, Value<'js>, Value<'js>, Value<'js>)> {
140 Ok((
141 self.flags,
142 self.value.into_js(ctx)?,
143 Undefined.into_js(ctx)?,
144 Undefined.into_js(ctx)?,
145 ))
146 }
147}
148
149impl<G> From<G> for Accessor<G, ()> {
150 fn from(get: G) -> Self {
151 Self {
152 get,
153 set: (),
154 flags: wrapper_impls!(@flag get),
155 }
156 }
157}
158
159impl<G> Accessor<G, ()> {
160 pub fn new_get(get: G) -> Self {
162 Self {
163 flags: wrapper_impls!(@flag get),
164 get,
165 set: (),
166 }
167 }
168
169 pub fn set<S>(self, set: S) -> Accessor<G, S> {
171 Accessor {
172 flags: self.flags | wrapper_impls!(@flag set),
173 get: self.get,
174 set,
175 }
176 }
177}
178
179impl<S> Accessor<(), S> {
180 pub fn new_set(set: S) -> Self {
182 Self {
183 flags: wrapper_impls!(@flag set),
184 get: (),
185 set,
186 }
187 }
188
189 pub fn get<G>(self, get: G) -> Accessor<G, S> {
191 Accessor {
192 flags: self.flags | wrapper_impls!(@flag get),
193 get,
194 set: self.set,
195 }
196 }
197}
198
199impl<G, S> Accessor<G, S> {
200 pub fn new(get: G, set: S) -> Self {
202 Self {
203 flags: wrapper_impls!(@flag get) | wrapper_impls!(@flag set),
204 get,
205 set,
206 }
207 }
208}
209
210impl<'js, G, GA> AsProperty<'js, (GA, (), ())> for Accessor<G, ()>
212where
213 G: IntoJsFunc<'js, GA> + 'js,
214{
215 fn config(self, ctx: &Ctx<'js>) -> Result<(PropertyFlags, Value<'js>, Value<'js>, Value<'js>)> {
216 Ok((
217 self.flags,
218 Undefined.into_js(ctx)?,
219 Function::new(ctx.clone(), self.get)?.into_value(),
220 Undefined.into_js(ctx)?,
221 ))
222 }
223}
224
225impl<'js, S, SA> AsProperty<'js, ((), (), SA)> for Accessor<(), S>
227where
228 S: IntoJsFunc<'js, SA> + 'js,
229{
230 fn config(self, ctx: &Ctx<'js>) -> Result<(PropertyFlags, Value<'js>, Value<'js>, Value<'js>)> {
231 Ok((
232 self.flags,
233 Undefined.into_js(ctx)?,
234 Undefined.into_js(ctx)?,
235 Function::new(ctx.clone(), self.set)?.into_value(),
236 ))
237 }
238}
239
240impl<'js, G, GA, S, SA> AsProperty<'js, (GA, SA)> for Accessor<G, S>
242where
243 G: IntoJsFunc<'js, GA> + 'js,
244 S: IntoJsFunc<'js, SA> + 'js,
245{
246 fn config(self, ctx: &Ctx<'js>) -> Result<(PropertyFlags, Value<'js>, Value<'js>, Value<'js>)> {
247 Ok((
248 self.flags,
249 Undefined.into_js(ctx)?,
250 Function::new(ctx.clone(), self.get)?.into_value(),
251 Function::new(ctx.clone(), self.set)?.into_value(),
252 ))
253 }
254}
255
256#[cfg(test)]
257mod test {
258 use crate::{object::*, *};
259
260 #[test]
261 fn property_with_undefined() {
262 test_with(|ctx| {
263 let obj = Object::new(ctx.clone()).unwrap();
264 obj.prop("key", ()).unwrap();
265
266 let _: () = obj.get("key").unwrap();
267
268 if let Err(Error::Exception) = obj.set("key", "") {
269 let exception = Exception::from_js(&ctx, ctx.catch()).unwrap();
270 assert_eq!(exception.message().as_deref(), Some("'key' is read-only"));
271 } else {
272 panic!("Should fail");
273 }
274 });
275 }
276
277 #[test]
278 fn property_with_value() {
279 test_with(|ctx| {
280 let obj = Object::new(ctx.clone()).unwrap();
281 obj.prop("key", "str").unwrap();
282
283 let s: StdString = obj.get("key").unwrap();
284 assert_eq!(s, "str");
285
286 if let Err(Error::Exception) = obj.set("key", "") {
287 let exception = Exception::from_js(&ctx, ctx.catch()).unwrap();
288 assert_eq!(exception.message().as_deref(), Some("'key' is read-only"));
289 } else {
290 panic!("Should fail");
291 }
292 });
293 }
294
295 #[test]
296 fn property_with_data_descriptor() {
297 test_with(|ctx| {
298 let obj = Object::new(ctx).unwrap();
299 obj.prop("key", Property::from("str")).unwrap();
300
301 let s: StdString = obj.get("key").unwrap();
302 assert_eq!(s, "str");
303 });
304 }
305
306 #[test]
307 #[should_panic(expected = "Error: 'key' is read-only")]
308 fn property_with_data_descriptor_readonly() {
309 test_with(|ctx| {
310 let obj = Object::new(ctx.clone()).unwrap();
311 obj.prop("key", Property::from("str")).unwrap();
312 obj.set("key", "text")
313 .catch(&ctx)
314 .map_err(|error| panic!("{}", error))
315 .unwrap();
316 });
317 }
318
319 #[test]
320 fn property_with_data_descriptor_writable() {
321 test_with(|ctx| {
322 let obj = Object::new(ctx).unwrap();
323 obj.prop("key", Property::from("str").writable()).unwrap();
324 obj.set("key", "text").unwrap();
325 });
326 }
327
328 #[test]
329 #[should_panic(expected = "Error: property is not configurable")]
330 fn property_with_data_descriptor_not_configurable() {
331 test_with(|ctx| {
332 let obj = Object::new(ctx.clone()).unwrap();
333 obj.prop("key", Property::from("str")).unwrap();
334 obj.prop("key", Property::from(39))
335 .catch(&ctx)
336 .map_err(|error| panic!("{}", error))
337 .unwrap();
338 });
339 }
340
341 #[test]
342 fn property_with_data_descriptor_configurable() {
343 test_with(|ctx| {
344 let obj = Object::new(ctx).unwrap();
345 obj.prop("key", Property::from("str").configurable())
346 .unwrap();
347 obj.prop("key", Property::from(39)).unwrap();
348 });
349 }
350
351 #[test]
352 fn property_with_data_descriptor_not_enumerable() {
353 test_with(|ctx| {
354 let obj = Object::new(ctx).unwrap();
355 obj.prop("key", Property::from("str")).unwrap();
356 let keys: Vec<StdString> = obj
357 .own_keys(object::Filter::new().string())
358 .collect::<Result<_>>()
359 .unwrap();
360 assert_eq!(keys.len(), 1);
361 assert_eq!(&keys[0], "key");
362 let keys: Vec<StdString> = obj.keys().collect::<Result<_>>().unwrap();
363 assert_eq!(keys.len(), 0);
364 });
365 }
366
367 #[test]
368 fn property_with_data_descriptor_enumerable() {
369 test_with(|ctx| {
370 let obj = Object::new(ctx).unwrap();
371 obj.prop("key", Property::from("str").enumerable()).unwrap();
372 let keys: Vec<StdString> = obj.keys().collect::<Result<_>>().unwrap();
373 assert_eq!(keys.len(), 1);
374 assert_eq!(&keys[0], "key");
375 });
376 }
377
378 #[test]
379 fn property_with_getter_only() {
380 test_with(|ctx| {
381 let obj = Object::new(ctx.clone()).unwrap();
382 obj.prop("key", Accessor::from(|| "str")).unwrap();
383
384 let s: StdString = obj.get("key").unwrap();
385 assert_eq!(s, "str");
386
387 if let Err(Error::Exception) = obj.set("key", "") {
388 let exception = Exception::from_js(&ctx, ctx.catch()).unwrap();
389 assert_eq!(
390 exception.message().as_deref(),
391 Some("no setter for property")
392 );
393 } else {
394 panic!("Should fail");
395 }
396 });
397 }
398
399 #[test]
400 fn property_with_getter_and_setter() {
401 test_with(|ctx| {
402 let val = Ref::new(Mut::new(StdString::new()));
403 let obj = Object::new(ctx).unwrap();
404 obj.prop(
405 "key",
406 Accessor::from({
407 let val = val.clone();
408 move || val.lock().clone()
409 })
410 .set({
411 let val = val.clone();
412 move |s| {
413 *val.lock() = s;
414 }
415 }),
416 )
417 .unwrap();
418
419 let s: StdString = obj.get("key").unwrap();
420 assert_eq!(s, "");
421
422 obj.set("key", "str").unwrap();
423 assert_eq!(val.lock().clone(), "str");
424
425 let s: StdString = obj.get("key").unwrap();
426 assert_eq!(s, "str");
427
428 obj.set("key", "").unwrap();
429 let s: StdString = obj.get("key").unwrap();
430 assert_eq!(s, "");
431 assert_eq!(val.lock().clone(), "");
432 });
433 }
434}