rust_jsc 0.5.0

High-level bindings to JavaScriptCore
Documentation
use std::ops::Deref;

use rust_jsc_sys::{JSObjectMakeDeferredPromise, JSValueRef};

use crate::{
    JSContext, JSError, JSObject, JSPromise, JSPromiseResolvingFunctions, JSResult,
    JSValue,
};

impl JSPromiseResolvingFunctions {
    pub fn resolve(
        &self,
        this: Option<&JSObject>,
        arguments: &[JSValue],
    ) -> JSResult<JSValue> {
        self.resolve.call(this, arguments)
    }

    pub fn protect(&self) {
        self.resolve.protect();
        self.reject.protect();
    }

    pub fn unprotect(&self) {
        self.resolve.unprotect();
        self.reject.unprotect();
    }

    pub fn reject(
        &self,
        this: Option<&JSObject>,
        arguments: &[JSValue],
    ) -> JSResult<JSValue> {
        self.reject.call(this, arguments)
    }
}

impl JSPromise {
    pub fn new_pending(ctx: &JSContext) -> JSResult<(Self, JSPromiseResolvingFunctions)> {
        let mut exception: JSValueRef = std::ptr::null_mut();
        let mut resolve = JSObject::new(ctx);
        let mut reject = JSObject::new(ctx);

        let result = unsafe {
            JSObjectMakeDeferredPromise(
                ctx.inner,
                &mut resolve.inner,
                &mut reject.inner,
                &mut exception,
            )
        };

        if !exception.is_null() {
            let value = JSValue::new(exception, ctx.inner);
            return Err(JSError::from(value));
        }

        let resolver = JSPromiseResolvingFunctions { resolve, reject };

        Ok((
            Self {
                this: JSObject::from_ref(result, ctx.inner),
                resolver: resolver.clone(),
            },
            resolver,
        ))
    }

    pub fn resolve(
        &self,
        this: Option<&JSObject>,
        arguments: &[JSValue],
    ) -> JSResult<JSValue> {
        self.resolver.resolve.call(this, arguments)
    }

    pub fn reject(
        &self,
        this: Option<&JSObject>,
        arguments: &[JSValue],
    ) -> JSResult<JSValue> {
        self.resolver.reject.call(this, arguments)
    }

    pub fn then(self, arguments: &[JSValue]) -> JSResult<JSValue> {
        self.this
            .get_property("then")?
            .as_object()?
            .call(Some(&self.this), arguments)
    }

    pub fn catch(&self, arguments: &[JSValue]) -> JSResult<JSValue> {
        self.this
            .get_property("catch")?
            .as_object()?
            .call(Some(&self.this), arguments)
    }

    pub fn finally(&self, arguments: &[JSValue]) -> JSResult<JSValue> {
        self.this
            .get_property("finally")?
            .as_object()?
            .call(Some(&self.this), arguments)
    }
}

impl Deref for JSPromise {
    type Target = JSValue;

    fn deref(&self) -> &JSValue {
        self.this.deref()
    }
}

impl From<JSPromise> for JSObject {
    fn from(promise: JSPromise) -> Self {
        promise.this
    }
}

impl From<JSPromise> for JSValue {
    fn from(promise: JSPromise) -> Self {
        promise.this.into()
    }
}

unsafe impl Send for JSPromise {}

#[cfg(test)]
mod tests {
    use crate::{self as rust_jsc, JSFunction, JSString};
    use rust_jsc_macros::callback;

    use crate::{JSContext, JSValue};

    use super::*;

    #[test]
    fn test_new_promise() {
        let ctx = JSContext::new();
        let (promise, _) = JSPromise::new_pending(&ctx).unwrap();
        assert_eq!(promise.is_object(), true);
    }

    #[test]
    fn test_resolve() {
        let ctx = JSContext::new();
        let (promise, _) = JSPromise::new_pending(&ctx).unwrap();
        let value = JSValue::number(&ctx, 42.0);
        let result = promise.resolve(None, &[value]).unwrap();
        assert_eq!(result.is_undefined(), true);
    }

    #[test]
    fn test_reject() {
        let ctx = JSContext::new();
        let (promise, _) = JSPromise::new_pending(&ctx).unwrap();
        let value = JSValue::number(&ctx, 42.0);
        let result = promise.reject(None, &[value]).unwrap();
        assert_eq!(result.is_undefined(), true);
    }

    #[test]
    fn test_resolve_function() {
        #[callback]
        fn log_info(
            ctx: JSContext,
            _function: JSObject,
            _this: JSObject,
            _arguments: &[JSValue],
        ) -> JSResult<JSValue> {
            let arg = _arguments.get(0).unwrap();
            println!("INFO: {}", arg.as_number().unwrap());

            assert_eq!(arg.as_number().unwrap(), 42.0);
            Ok(JSValue::undefined(&ctx))
        }

        let ctx = JSContext::new();
        let (promise, resolver) = JSPromise::new_pending(&ctx).unwrap();
        let value = JSValue::number(&ctx, 42.0);

        resolver.resolve(None, &[value]).unwrap();
        let function = JSFunction::callback::<JSString>(&ctx, None, Some(log_info));
        let result = promise.then(&[function.into()]);

        assert_eq!(result.unwrap().is_object(), true);
    }
}