use crate::{qjs, Ctx, IntoJs, Object, Result};
mod handler;
pub use handler::{ProxyHandler, ProxyProperty, ProxyReceiver, ProxyTarget};
#[derive(Debug, PartialEq, Clone, Hash, Eq)]
#[repr(transparent)]
pub struct Proxy<'js>(pub(crate) Object<'js>);
impl<'js> Proxy<'js> {
pub fn new(
ctx: Ctx<'js>,
target: impl IntoJs<'js>,
handler: ProxyHandler<'js>,
) -> Result<Self> {
let proxy = unsafe {
let target = target.into_js(&ctx)?;
let handler = handler.0;
let value = qjs::JS_NewProxy(ctx.as_ptr(), target.as_js_value(), handler.as_js_value());
let value = ctx.handle_exception(value)?;
Object::from_js_value(ctx, value)
};
Ok(Self(proxy))
}
pub fn target(&self) -> Result<Object<'js>> {
unsafe {
let target = qjs::JS_GetProxyTarget(self.0.ctx.as_ptr(), self.0.as_js_value());
let target = self.0.ctx.handle_exception(target)?;
Ok(Object::from_js_value(self.0.ctx.clone(), target))
}
}
pub fn handler(&self) -> Result<Object<'js>> {
unsafe {
let handler = qjs::JS_GetProxyHandler(self.0.ctx.as_ptr(), self.0.as_js_value());
let handler = self.0.ctx.handle_exception(handler)?;
Ok(Object::from_js_value(self.0.ctx.clone(), handler))
}
}
}
#[cfg(test)]
mod test {
use crate::{
class::{JsClass, Readable, Trace, Tracer},
test_with,
value::Constructor,
Class, Error, Function, JsLifetime, Value,
};
use super::*;
#[test]
fn from_javascript() {
test_with(|ctx| {
let proxy: Proxy = ctx
.eval(r#"new Proxy({ a: 1 }, { get: () => 2 })"#)
.unwrap();
let target = proxy.target().unwrap();
let handler = proxy.handler().unwrap();
let a: i32 = target.get("a").unwrap();
assert_eq!(a, 1);
let _: Function = handler.get("get").unwrap();
});
}
#[test]
fn from_rust() {
test_with(|ctx| {
let handler = ProxyHandler::new(ctx.clone())
.unwrap()
.with_getter(|target, property, _receiver| {
if property.to_string().unwrap() == "a" {
let value: Value<'_> = target.0.get("a")?;
Ok(value)
} else {
Err(Error::Unknown)
}
})
.unwrap();
let target = Object::new(ctx.clone()).unwrap();
target.set("a", 1).unwrap();
let proxy = Proxy::new(ctx.clone(), target, handler).unwrap();
ctx.globals().set("proxy", proxy).unwrap();
let a: i32 = ctx.eval("proxy.a").unwrap();
assert_eq!(a, 1);
});
}
#[test]
fn class_proxy() {
pub struct MyClass {
a: i32,
}
impl MyClass {
pub fn new(a: i32) -> Self {
Self { a }
}
}
impl<'js> Trace<'js> for MyClass {
fn trace<'a>(&self, _tracer: Tracer<'a, 'js>) {}
}
unsafe impl<'js> JsLifetime<'js> for MyClass {
type Changed<'to> = MyClass;
}
impl<'js> JsClass<'js> for MyClass {
const NAME: &'static str = "MyClass";
type Mutable = Readable;
fn constructor(_ctx: &Ctx<'js>) -> Result<Option<Constructor<'js>>> {
Ok(None)
}
}
test_with(|ctx| {
let handler = ProxyHandler::new(ctx.clone())
.unwrap()
.with_getter(|target, property, _receiver| {
if property.to_string().unwrap() == "a" {
let target = target.0.into_class::<MyClass>().unwrap();
let value = target.borrow().a;
Ok(value)
} else {
Err(Error::Unknown)
}
})
.unwrap();
let target = Class::instance(ctx.clone(), MyClass::new(1)).unwrap();
let proxy = Proxy::new(ctx.clone(), target, handler).unwrap();
ctx.globals().set("proxy", proxy).unwrap();
let a: i32 = ctx.eval("proxy.a").unwrap();
assert_eq!(a, 1);
});
}
}