rquickjs_core/value/
proxy.rs

1//! Module for types dealing with JS proxies.
2
3use crate::{qjs, Ctx, IntoJs, Object, Result};
4
5mod handler;
6pub use handler::{ProxyHandler, ProxyProperty, ProxyReceiver, ProxyTarget};
7
8/// Rust representation of a JavaScript proxy.
9#[derive(Debug, PartialEq, Clone, Hash, Eq)]
10#[repr(transparent)]
11pub struct Proxy<'js>(pub(crate) Object<'js>);
12
13impl<'js> Proxy<'js> {
14    /// Create a new JavaScript proxy
15    pub fn new(
16        ctx: Ctx<'js>,
17        target: impl IntoJs<'js>,
18        handler: ProxyHandler<'js>,
19    ) -> Result<Self> {
20        let proxy = unsafe {
21            let target = target.into_js(&ctx)?;
22            let handler = handler.0;
23            let value = qjs::JS_NewProxy(ctx.as_ptr(), target.as_js_value(), handler.as_js_value());
24            let value = ctx.handle_exception(value)?;
25            Object::from_js_value(ctx, value)
26        };
27        Ok(Self(proxy))
28    }
29
30    /// Get the target of the proxy
31    pub fn target(&self) -> Result<Object<'js>> {
32        unsafe {
33            let target = qjs::JS_GetProxyTarget(self.0.ctx.as_ptr(), self.0.as_js_value());
34            let target = self.0.ctx.handle_exception(target)?;
35            Ok(Object::from_js_value(self.0.ctx.clone(), target))
36        }
37    }
38
39    /// Get the handler of the proxy
40    pub fn handler(&self) -> Result<Object<'js>> {
41        unsafe {
42            let handler = qjs::JS_GetProxyHandler(self.0.ctx.as_ptr(), self.0.as_js_value());
43            let handler = self.0.ctx.handle_exception(handler)?;
44            Ok(Object::from_js_value(self.0.ctx.clone(), handler))
45        }
46    }
47}
48
49#[cfg(test)]
50mod test {
51    use crate::{
52        class::{JsClass, Readable, Trace, Tracer},
53        test_with,
54        value::Constructor,
55        Class, Error, Function, JsLifetime, Value,
56    };
57
58    use super::*;
59
60    #[test]
61    fn from_javascript() {
62        test_with(|ctx| {
63            let proxy: Proxy = ctx
64                .eval(r#"new Proxy({ a: 1 }, { get: () => 2 })"#)
65                .unwrap();
66            let target = proxy.target().unwrap();
67            let handler = proxy.handler().unwrap();
68            let a: i32 = target.get("a").unwrap();
69            assert_eq!(a, 1);
70            let _: Function = handler.get("get").unwrap();
71        });
72    }
73
74    #[test]
75    fn from_rust() {
76        test_with(|ctx| {
77            let handler = ProxyHandler::new(ctx.clone())
78                .unwrap()
79                .with_getter(|target, property, _receiver| {
80                    if property.to_string().unwrap() == "a" {
81                        let value: Value<'_> = target.0.get("a")?;
82                        Ok(value)
83                    } else {
84                        Err(Error::Unknown)
85                    }
86                })
87                .unwrap();
88            let target = Object::new(ctx.clone()).unwrap();
89            target.set("a", 1).unwrap();
90            let proxy = Proxy::new(ctx.clone(), target, handler).unwrap();
91            ctx.globals().set("proxy", proxy).unwrap();
92            let a: i32 = ctx.eval("proxy.a").unwrap();
93            assert_eq!(a, 1);
94        });
95    }
96
97    #[test]
98    fn class_proxy() {
99        pub struct MyClass {
100            a: i32,
101        }
102
103        impl MyClass {
104            pub fn new(a: i32) -> Self {
105                Self { a }
106            }
107        }
108
109        impl<'js> Trace<'js> for MyClass {
110            fn trace<'a>(&self, _tracer: Tracer<'a, 'js>) {}
111        }
112
113        unsafe impl<'js> JsLifetime<'js> for MyClass {
114            type Changed<'to> = MyClass;
115        }
116
117        impl<'js> JsClass<'js> for MyClass {
118            const NAME: &'static str = "MyClass";
119
120            type Mutable = Readable;
121
122            fn constructor(_ctx: &Ctx<'js>) -> Result<Option<Constructor<'js>>> {
123                Ok(None)
124            }
125        }
126
127        test_with(|ctx| {
128            let handler = ProxyHandler::new(ctx.clone())
129                .unwrap()
130                .with_getter(|target, property, _receiver| {
131                    if property.to_string().unwrap() == "a" {
132                        let target = target.0.into_class::<MyClass>().unwrap();
133                        let value = target.borrow().a;
134                        Ok(value)
135                    } else {
136                        Err(Error::Unknown)
137                    }
138                })
139                .unwrap();
140            let target = Class::instance(ctx.clone(), MyClass::new(1)).unwrap();
141            let proxy = Proxy::new(ctx.clone(), target, handler).unwrap();
142            ctx.globals().set("proxy", proxy).unwrap();
143            let a: i32 = ctx.eval("proxy.a").unwrap();
144            assert_eq!(a, 1);
145        });
146    }
147}