diff --git a/Cargo.toml b/Cargo.toml index f46ef74..37881d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ license = "Redis Source Available License 2.0 (RSALv2) or the Server Side Public [dependencies] [build-dependencies] -bindgen = "0.59.2" +bindgen = "0.59" vergen = { version = "8", features = ["git", "gitcl"] } lazy_static = "1" diff --git a/src/lib.rs b/src/lib.rs index 930cd9f..aadd681 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ */ //! V8-rs is a crate containing bindings to the V8 C++ API. -// #![deny(missing_docs)] +#![deny(missing_docs)] /// The module contains the rust-idiomatic data structures and functions. pub mod v8; @@ -56,11 +56,14 @@ impl From for RawIndex { #[cfg(test)] mod json_path_tests { use crate as v8_rs; - use crate::v8::{ - isolate, isolate_scope, v8_array, v8_array_buffer, v8_context_scope, v8_init, - v8_native_function_template, v8_object, v8_set, v8_utf8, - v8_value::{self}, - }; + use crate::v8::types::any::LocalValueAny; + use crate::v8::types::native_function_template::LocalNativeFunctionTemplate; + use crate::v8::types::object_template::LocalObjectTemplate; + use crate::v8::types::promise::LocalPromise; + use crate::v8::types::try_catch::TryCatch; + use crate::v8::types::utf8::LocalUtf8; + use crate::v8::types::Value; + use crate::v8::{context_scope, isolate, isolate_scope, types, v8_init}; use v8_derive::new_native_function; @@ -83,32 +86,32 @@ mod json_path_tests { #[test] fn test_simple_isolate_creation() { initialize(); - let isolate = isolate::V8Isolate::new(); + let isolate = isolate::Isolate::new(); let _i_scope = isolate.enter(); } #[test] fn test_simple_string_creation() { initialize(); - let isolate = isolate::V8Isolate::new(); + let isolate = isolate::Isolate::new(); let isolate_scope = isolate.enter(); - let _s = isolate_scope.new_string("test"); + let _s = isolate_scope.create_string("test"); } #[test] fn test_simple_object_creation() { initialize(); - let isolate = isolate::V8Isolate::new(); + let isolate = isolate::Isolate::new(); let isolate_scope = isolate.enter(); - let _o = isolate_scope.new_object_template(); + let _o = isolate_scope.create_object_template(); } #[test] fn test_simple_native_function_creation() { initialize(); - let isolate = isolate::V8Isolate::new(); + let isolate = isolate::Isolate::new(); let isolate_scope = isolate.enter(); - let _o = isolate_scope.new_native_function_template(|_args, _isolate, _ctx_scope| { + let _o = isolate_scope.create_native_function_template(|_args, _isolate, _ctx_scope| { println!("test"); None }); @@ -117,20 +120,23 @@ mod json_path_tests { #[test] fn test_native_function_args() { initialize(); - let isolate = isolate::V8Isolate::new(); + let isolate = isolate::Isolate::new(); let isolate_scope = isolate.enter(); - let native = - isolate_scope.new_native_function_template(|args, _isolate_scope, _ctx_scope| { + let native = isolate_scope + .create_native_function_template(|args, _isolate_scope, _ctx_scope| { let v = args.get(0); - let s = v.to_utf8().unwrap(); + let s: LocalUtf8 = v.try_into().unwrap(); assert_eq!(s.as_str(), "2"); None - }); - let native_funciton_name = isolate_scope.new_string("foo"); - let mut globals = isolate_scope.new_object_template(); + }) + .try_into() + .unwrap(); + let native_funciton_name = isolate_scope.create_string("foo").try_into().unwrap(); + let mut globals: LocalObjectTemplate = + isolate_scope.create_object_template().try_into().unwrap(); globals.set_native_function(&native_funciton_name, &native); - let code_str = isolate_scope.new_string("foo(2)"); - let ctx = isolate_scope.new_context(Some(&globals)); + let code_str = isolate_scope.create_string("foo(2)").try_into().unwrap(); + let ctx = isolate_scope.create_context(Some(&globals)); let ctx_scope = ctx.enter(&isolate_scope); let script = ctx_scope.compile(&code_str).unwrap(); script.run(&ctx_scope).unwrap(); @@ -139,32 +145,41 @@ mod json_path_tests { #[test] fn test_native_function_call_js() { initialize(); - let isolate = isolate::V8Isolate::new(); + let isolate = isolate::Isolate::new(); let isolate_scope = isolate.enter(); - let foo1 = isolate_scope.new_native_function_template(|args, _isolate, ctx_scope| { - let v = args.get(0); - let _res = v.call(ctx_scope, None); - None - }); - let foo1_name = isolate_scope.new_string("foo1"); + let foo1 = isolate_scope + .create_native_function_template(|args, _isolate, ctx_scope| { + let v: LocalValueAny = args.get(0).try_into().unwrap(); + let _res = v.call(ctx_scope, None); + None + }) + .try_into() + .unwrap(); + let foo1_name = isolate_scope.create_string("foo1").try_into().unwrap(); - let foo2 = - isolate_scope.new_native_function_template(|args, _isolate_scope, _ctx_scope| { + let foo2 = isolate_scope + .create_native_function_template(|args, _isolate_scope, _ctx_scope| { let v = args.get(0); - let s = v.to_utf8().unwrap(); + let s: LocalUtf8 = v.try_into().unwrap(); assert_eq!(s.as_str(), "2"); None - }); - let foo2_name = isolate_scope.new_string("foo2"); + }) + .try_into() + .unwrap(); + let foo2_name = isolate_scope.create_string("foo2").try_into().unwrap(); - let mut globals = isolate_scope.new_object_template(); + let mut globals: LocalObjectTemplate = + isolate_scope.create_object_template().try_into().unwrap(); globals.set_native_function(&foo1_name, &foo1); globals.set_native_function(&foo2_name, &foo2); - let code_str = isolate_scope.new_string("foo1(()=>{foo2(2)})"); + let code_str = isolate_scope + .create_string("foo1(()=>{foo2(2)})") + .try_into() + .unwrap(); let i_scope = isolate.enter(); - let ctx = i_scope.new_context(Some(&globals)); + let ctx = i_scope.create_context(Some(&globals)); let ctx_scope = ctx.enter(&isolate_scope); let script = ctx_scope.compile(&code_str).unwrap(); script.run(&ctx_scope).unwrap(); @@ -173,32 +188,41 @@ mod json_path_tests { #[test] fn test_native_function_call_with_args() { initialize(); - let isolate = isolate::V8Isolate::new(); + let isolate = isolate::Isolate::new(); let isolate_scope = isolate.enter(); - let foo1 = isolate_scope.new_native_function_template(|args, isolate_scope, ctx_scope| { - let foo = isolate_scope.new_string("foo"); - let v = args.get(0); - let _res = v.call(ctx_scope, Some(&[&foo.to_value()])); - None - }); - let foo1_name = isolate_scope.new_string("foo1"); + let foo1 = isolate_scope + .create_native_function_template(|args, isolate_scope, ctx_scope| { + let foo: LocalValueAny = isolate_scope.create_string("foo").try_into().unwrap(); + let v: LocalValueAny = args.get(0).try_into().unwrap(); + let _res = v.call(ctx_scope, Some(&[&foo])); + None + }) + .try_into() + .unwrap(); + let foo1_name = isolate_scope.create_string("foo1").try_into().unwrap(); - let foo2 = - isolate_scope.new_native_function_template(|args, _isolate_scope, _ctx_scope| { + let foo2 = isolate_scope + .create_native_function_template(|args, _isolate_scope, _ctx_scope| { let v = args.get(0); - let s = v.to_utf8().unwrap(); + let s: LocalUtf8 = v.try_into().unwrap(); assert_eq!(s.as_str(), "foo"); None - }); - let foo2_name = isolate_scope.new_string("foo2"); + }) + .try_into() + .unwrap(); + let foo2_name = isolate_scope.create_string("foo2").try_into().unwrap(); - let mut globals = isolate_scope.new_object_template(); + let mut globals: LocalObjectTemplate = + isolate_scope.create_object_template().try_into().unwrap(); globals.set_native_function(&foo1_name, &foo1); globals.set_native_function(&foo2_name, &foo2); - let code_str = isolate_scope.new_string("foo1((a)=>{foo2(a)})"); - let ctx = isolate_scope.new_context(Some(&globals)); + let code_str = isolate_scope + .create_string("foo1((a)=>{foo2(a)})") + .try_into() + .unwrap(); + let ctx = isolate_scope.create_context(Some(&globals)); let ctx_scope = ctx.enter(&isolate_scope); let script = ctx_scope.compile(&code_str).unwrap(); script.run(&ctx_scope).unwrap(); @@ -207,165 +231,194 @@ mod json_path_tests { #[test] fn test_native_function_raise_exception() { initialize(); - let isolate = isolate::V8Isolate::new(); + let isolate = isolate::Isolate::new(); let isolate_scope = isolate.enter(); - let native = isolate_scope.new_native_function_template(|_args, isolate, _ctx_scope| { - isolate.raise_exception_str("this is an error"); - None - }); - let native_funciton_name = isolate_scope.new_string("foo"); - let mut globals = isolate_scope.new_object_template(); + let native = isolate_scope + .create_native_function_template(|_args, isolate, _ctx_scope| { + isolate.raise_exception_str("this is an error"); + None + }) + .try_into() + .unwrap(); + let native_funciton_name = isolate_scope.create_string("foo").try_into().unwrap(); + let mut globals: LocalObjectTemplate = + isolate_scope.create_object_template().try_into().unwrap(); globals.set_native_function(&native_funciton_name, &native); - let code_str = isolate_scope.new_string("foo(2)"); - let ctx = isolate_scope.new_context(Some(&globals)); + let code_str = isolate_scope.create_string("foo(2)").try_into().unwrap(); + let ctx = isolate_scope.create_context(Some(&globals)); let ctx_scope = ctx.enter(&isolate_scope); let script = ctx_scope.compile(&code_str).unwrap(); - let trycatch = isolate_scope.new_try_catch(); + let trycatch: TryCatch = isolate_scope.create_try_catch().try_into().unwrap(); assert!(script.run(&ctx_scope).is_none()); let exception = trycatch.get_exception(); - let exception_msg = exception.to_utf8().unwrap(); + let exception_msg = LocalUtf8::try_from(exception).unwrap(); assert_eq!(exception_msg.as_str(), "this is an error"); } #[test] fn test_native_function_raise_exception_error() { initialize(); - let isolate = isolate::V8Isolate::new(); + let isolate = isolate::Isolate::new(); let isolate_scope = isolate.enter(); let code_str = isolate_scope - .new_string("function foo(){throw new Error('this is an error!');};foo();"); - let ctx = isolate_scope.new_context(None); + .create_string("function foo(){throw new Error('this is an error!');};foo();") + .try_into() + .unwrap(); + let ctx = isolate_scope.create_context(None); let ctx_scope = ctx.enter(&isolate_scope); let script = ctx_scope.compile(&code_str).unwrap(); - let trycatch = isolate_scope.new_try_catch(); + let trycatch: TryCatch = isolate_scope.create_try_catch().try_into().unwrap(); assert!(script.run(&ctx_scope).is_none()); let exception = trycatch.get_exception(); - let exception_msg = exception.to_utf8().unwrap(); + let exception_msg = LocalUtf8::try_from(exception).unwrap(); assert_eq!(exception_msg.as_str(), "Error: this is an error!"); let trace = trycatch.get_trace(&ctx_scope); - let trace_str = trace.unwrap().to_utf8().unwrap(); + let trace_str = LocalUtf8::try_from(trace.unwrap()).unwrap(); assert!(trace_str.as_str().contains("at foo")); } #[test] fn test_simple_code_run() { initialize(); - let isolate = isolate::V8Isolate::new(); + let isolate = isolate::Isolate::new(); let isolate_scope = isolate.enter(); - let code_str = isolate_scope.new_string("1+1"); - let ctx = isolate_scope.new_context(None); + let code_str = isolate_scope.create_string("1+1").try_into().unwrap(); + let ctx = isolate_scope.create_context(None); let ctx_scope = ctx.enter(&isolate_scope); let script = ctx_scope.compile(&code_str).unwrap(); let res = script.run(&ctx_scope).unwrap(); - let res_utf8 = res.to_utf8().unwrap(); + let res_utf8: LocalUtf8 = res.try_into().unwrap(); assert_eq!(res_utf8.as_str(), "2"); } #[test] fn test_simple_module_run() { initialize(); - let isolate = isolate::V8Isolate::new(); + let isolate = isolate::Isolate::new(); let isolate_scope = isolate.enter(); - let mut globals = isolate_scope.new_object_template(); + let mut globals: LocalObjectTemplate = + isolate_scope.create_object_template().try_into().unwrap(); globals.add_native_function("log", |args, _isolate_scope, _ctx_scope| { assert_eq!(args.len(), 1); let v = args.get(0); - let res_utf8 = v.to_utf8().unwrap(); + let res_utf8: LocalUtf8 = v.try_into().unwrap(); assert_eq!(res_utf8.as_str(), "foo"); None }); - let code_name = isolate_scope.new_string("base_module"); - let code_str = isolate_scope.new_string("import {msg} from \"foo\"; log(msg);"); - let ctx = isolate_scope.new_context(Some(&globals)); + let code_name = isolate_scope + .create_string("base_module") + .try_into() + .unwrap(); + let code_str = isolate_scope + .create_string("import {msg} from \"foo\"; log(msg);") + .try_into() + .unwrap(); + let ctx = isolate_scope.create_context(Some(&globals)); let ctx_scope = ctx.enter(&isolate_scope); let module = ctx_scope .compile_as_module(&code_name, &code_str, true) .unwrap(); - module.initialize( - &ctx_scope, - |isolate_scope, ctx_scope, name_to_load, _identity_hash| { - let code_str = isolate_scope.new_string("export let msg = \"foo\";"); - ctx_scope.compile_as_module(name_to_load, &code_str, true) - }, - ); + let module = module + .initialize( + &ctx_scope, + |isolate_scope, ctx_scope, name_to_load, _identity_hash| { + let code_str = isolate_scope + .create_string("export let msg = \"foo\";") + .try_into() + .unwrap(); + ctx_scope.compile_as_module(name_to_load, &code_str, true) + }, + ) + .unwrap(); let res = module.evaluate(&ctx_scope).unwrap(); - let res = res.as_promise(); + let res: LocalPromise = res.try_into().unwrap(); assert_eq!( - res.state(), - crate::v8::v8_promise::V8PromiseState::Fulfilled + res.state().unwrap(), + crate::v8::types::promise::PromiseState::Fulfilled ); } #[test] fn test_async_function() { initialize(); - let isolate = isolate::V8Isolate::new(); + let isolate = isolate::Isolate::new(); let isolate_scope = isolate.enter(); - let code_str = isolate_scope.new_string("async function f(){return 1}; f"); - let ctx = isolate_scope.new_context(None); + let code_str = isolate_scope + .create_string("async function f(){return 1}; f") + .try_into() + .unwrap(); + let ctx = isolate_scope.create_context(None); let ctx_scope = ctx.enter(&isolate_scope); let script = ctx_scope.compile(&code_str).unwrap(); let res = script.run(&ctx_scope).unwrap(); + let res: LocalValueAny = res.try_into().unwrap(); assert!(res.is_async_function()); let async_res = res.call(&ctx_scope, None).unwrap(); - assert!(async_res.is_promise()); - let promise = async_res.as_promise(); + let promise: LocalPromise = async_res.try_into().unwrap(); assert_eq!( - promise.state(), - crate::v8::v8_promise::V8PromiseState::Fulfilled + promise.state().unwrap(), + crate::v8::types::promise::PromiseState::Fulfilled ); let promise_res = promise.get_result(); - let res_utf8 = promise_res.to_utf8().unwrap(); + let res_utf8 = LocalUtf8::try_from(promise_res).unwrap(); assert_eq!(res_utf8.as_str(), "1"); } #[test] fn test_promise_resolver() { initialize(); - let isolate = isolate::V8Isolate::new(); + let isolate = isolate::Isolate::new(); let isolate_scope = isolate.enter(); - let mut globals = isolate_scope.new_object_template(); + let mut globals: LocalObjectTemplate = + isolate_scope.create_object_template().try_into().unwrap(); globals.add_native_function("foo", |_args, isolate_scope, ctx_scope| { - let resolver = ctx_scope.new_resolver(); - resolver.resolve(&ctx_scope, &isolate_scope.new_string("foo").to_value()); + let resolver = ctx_scope.create_resolver(); + resolver.resolve( + ctx_scope, + &isolate_scope.create_string("foo").try_into().unwrap(), + ); let promise = resolver.get_promise(); - let promise_val = promise.to_value(); - Some(promise_val) + // let promise_val = promise.to_value(); + Some(promise.try_into().unwrap()) }); - let code_str = isolate_scope.new_string("foo()"); - let ctx = isolate_scope.new_context(Some(&globals)); + let code_str = isolate_scope.create_string("foo()").try_into().unwrap(); + let ctx = isolate_scope.create_context(Some(&globals)); let ctx_scope = ctx.enter(&isolate_scope); - let script = ctx_scope.compile(&code_str).unwrap(); + let script = ctx_scope + .compile(&code_str) + .expect("Couldn't compile script"); let res = script.run(&ctx_scope).unwrap(); - println!("{}", res.to_utf8().unwrap().as_str()); - assert!(res.is_promise()); - let promise = res.as_promise(); + let s: LocalUtf8 = res.clone().try_into().unwrap(); + println!("{}", s.as_str()); + let promise: LocalPromise = res.try_into().unwrap(); assert_eq!( - promise.state(), - crate::v8::v8_promise::V8PromiseState::Fulfilled + promise.state().unwrap(), + crate::v8::types::promise::PromiseState::Fulfilled ); let promise_res = promise.get_result(); - let res_utf8 = promise_res.to_utf8().unwrap(); + let res_utf8 = LocalUtf8::try_from(promise_res).unwrap(); assert_eq!(res_utf8.as_str(), "foo"); } #[test] fn test_compilation_error() { initialize(); - let isolate = isolate::V8Isolate::new(); + let isolate = isolate::Isolate::new(); let isolate_scope = isolate.enter(); - let code_str = isolate_scope.new_string("foo("); - let ctx = isolate_scope.new_context(None); + let code_str = isolate_scope.create_string("foo(").try_into().unwrap(); + let ctx = isolate_scope.create_context(None); let ctx_scope = ctx.enter(&isolate_scope); - let trycatch = isolate_scope.new_try_catch(); + let trycatch: TryCatch = isolate_scope.create_try_catch().try_into().unwrap(); let script = ctx_scope.compile(&code_str); assert!(script.is_none()); assert_eq!( - trycatch.get_exception().to_utf8().unwrap().as_str(), + LocalUtf8::try_from(trycatch.get_exception()) + .unwrap() + .as_str(), "SyntaxError: Unexpected end of input" ); } @@ -373,49 +426,53 @@ mod json_path_tests { #[test] fn test_run_error() { initialize(); - let isolate = isolate::V8Isolate::new(); + let isolate = isolate::Isolate::new(); let isolate_scope = isolate.enter(); - let code_str = isolate_scope.new_string("foo()"); - let ctx = isolate_scope.new_context(None); + let code_str = isolate_scope.create_string("foo()").try_into().unwrap(); + let ctx = isolate_scope.create_context(None); let ctx_scope = ctx.enter(&isolate_scope); - let trycatch = isolate_scope.new_try_catch(); + let trycatch: TryCatch = isolate_scope.create_try_catch().try_into().unwrap(); let script = ctx_scope.compile(&code_str).unwrap(); let res = script.run(&ctx_scope); assert!(res.is_none()); assert_eq!( - trycatch.get_exception().to_utf8().unwrap().as_str(), + LocalUtf8::try_from(trycatch.get_exception()) + .unwrap() + .as_str(), "ReferenceError: foo is not defined" ); } fn define_function_and_call< F: for<'d, 'e> Fn( - &v8_native_function_template::V8LocalNativeFunctionArgs<'d, 'e>, - &'d isolate_scope::V8IsolateScope<'e>, - &v8_context_scope::V8ContextScope<'d, 'e>, - ) -> Option>, + &types::native_function_template::LocalNativeFunctionArgs<'d, 'e>, + &'d isolate_scope::IsolateScope<'e>, + &context_scope::ContextScope<'d, 'e>, + ) -> Option>, >( code: &str, func_name: &str, f: F, ) -> Result<(), String> { initialize(); - let isolate = isolate::V8Isolate::new(); + let isolate = isolate::Isolate::new(); let isolate_scope = isolate.enter(); - let native = isolate_scope.new_native_function_template(f); - let native_funciton_name = isolate_scope.new_string(func_name); - let mut globals = isolate_scope.new_object_template(); + let native: LocalNativeFunctionTemplate = isolate_scope + .create_native_function_template(f) + .try_into() + .unwrap(); + let native_funciton_name = isolate_scope.create_string(func_name).try_into().unwrap(); + let mut globals: LocalObjectTemplate = + isolate_scope.create_object_template().try_into().unwrap(); globals.set_native_function(&native_funciton_name, &native); - let code_str = isolate_scope.new_string(code); - let ctx = isolate_scope.new_context(Some(&globals)); + let code_str = isolate_scope.create_string(code).try_into().unwrap(); + let ctx = isolate_scope.create_context(Some(&globals)); let ctx_scope = ctx.enter(&isolate_scope); let script = ctx_scope.compile(&code_str).unwrap(); - let trycatch = isolate_scope.new_try_catch(); + let trycatch: TryCatch = isolate_scope.create_try_catch().try_into().unwrap(); let res = match script.run(&ctx_scope) { Some(_res) => Ok(()), - None => Err(trycatch - .get_exception() - .to_utf8() + None => Err(LocalUtf8::try_from(trycatch.get_exception()) .unwrap() .as_str() .to_string()), @@ -435,7 +492,11 @@ mod json_path_tests { #[test] fn test_value_is_function() { define_function_and_call("foo(()=>{})", "foo", |args, _isolate, _ctx_scope| { - assert!(args.get(0).is_function()); + if let Value::Other(any) = args.get(0) { + assert!(any.is_function()); + } else { + unreachable!("The value should have been a function!"); + } None }) .expect("Got error on function run"); @@ -447,7 +508,11 @@ mod json_path_tests { "foo(async function(){})", "foo", |args, _isolate, _ctx_scope| { - assert!(args.get(0).is_async_function()); + if let Value::Other(any) = args.get(0) { + assert!(any.is_async_function()); + } else { + unreachable!("The value should have been an async function!"); + } None }, ) @@ -493,7 +558,7 @@ mod json_path_tests { new_native_function!(|_isolate, _ctx_scope, arg1: i64, arg2: i64| { assert_eq!(arg1, 1); assert_eq!(arg2, 2); - Result::, String>::Ok(None) + Result::, String>::Ok(None) }), ) .expect("Got error on function run"); @@ -507,7 +572,7 @@ mod json_path_tests { new_native_function!(|_isolate, _ctx_scope, arg1: i64, arg2: f64| { assert_eq!(arg1, 1); assert_eq!(arg2, 2.2); - Result::, String>::Ok(None) + Result::, String>::Ok(None) }), ) .expect("Got error on function run"); @@ -522,7 +587,7 @@ mod json_path_tests { assert_eq!(arg1, 1); assert_eq!(arg2, 2.2); assert_eq!(arg3, "test"); - Result::, String>::Ok(None) + Result::, String>::Ok(None) }), ) .expect("Got error on function run"); @@ -536,8 +601,8 @@ mod json_path_tests { new_native_function!(|_isolate, _ctx_scope, arg1: i64, arg2: f64, arg3: bool| { assert_eq!(arg1, 1); assert_eq!(arg2, 2.2); - assert_eq!(arg3, true); - Result::, String>::Ok(None) + assert!(arg3); + Result::, String>::Ok(None) }), ) .expect("Got error on function run"); @@ -548,9 +613,9 @@ mod json_path_tests { define_function_and_call( "test('test')", "test", - new_native_function!(|_isolate, _ctx_scope, arg1: v8_utf8::V8LocalUtf8| { + new_native_function!(|_isolate, _ctx_scope, arg1: types::utf8::LocalUtf8| { assert_eq!(arg1.as_str(), "test"); - Result::, String>::Ok(None) + Result::, String>::Ok(None) }), ) .expect("Got error on function run"); @@ -561,9 +626,10 @@ mod json_path_tests { define_function_and_call( "test('test')", "test", - new_native_function!(|_isolate, _ctx_scope, arg1: v8_value::V8LocalValue| { - assert_eq!(arg1.to_utf8().unwrap().as_str(), "test"); - Result::, String>::Ok(None) + new_native_function!(|_isolate, _ctx_scope, arg1: Value| { + let utf8 = LocalUtf8::try_from(arg1).unwrap(); + assert_eq!(utf8.as_ref(), "test"); + Result::, String>::Ok(None) }), ) .expect("Got error on function run"); @@ -574,8 +640,8 @@ mod json_path_tests { define_function_and_call( "test(new Set())", "test", - new_native_function!(|_isolate, _ctx_scope, _arg1: v8_set::V8LocalSet| { - Result::, String>::Ok(None) + new_native_function!(|_isolate, _ctx_scope, _arg1: types::set::LocalSet| { + Result::, String>::Ok(None) }), ) .expect("Got error on function run"); @@ -586,9 +652,9 @@ mod json_path_tests { define_function_and_call( "test([1, 2])", "test", - new_native_function!(|_isolate, _ctx_scope, arg1: v8_array::V8LocalArray| { + new_native_function!(|_isolate, _ctx_scope, arg1: types::array::LocalArray| { assert_eq!(arg1.len(), 2); - Result::, String>::Ok(None) + Result::, String>::Ok(None) }), ) .expect("Got error on function run"); @@ -600,9 +666,9 @@ mod json_path_tests { "test(new Uint8Array([255, 255, 255, 255]).buffer)", "test", new_native_function!( - |_isolate, _ctx_scope, arg1: v8_array_buffer::V8LocalArrayBuffer| { + |_isolate, _ctx_scope, arg1: types::array_buffer::LocalArrayBuffer| { assert_eq!(arg1.data(), &[255, 255, 255, 255]); - Result::, String>::Ok(None) + Result::, String>::Ok(None) } ), ) @@ -615,18 +681,18 @@ mod json_path_tests { "test({'foo':'bar'})", "test", new_native_function!( - |isolate_scope: &isolate_scope::V8IsolateScope, + |isolate_scope: &isolate_scope::IsolateScope, ctx_scope, - arg1: v8_object::V8LocalObject| { - assert_eq!( - arg1.get(ctx_scope, &isolate_scope.new_string("foo").to_value()) - .unwrap() - .to_utf8() - .unwrap() - .as_str(), - "bar" - ); - Result::, String>::Ok(None) + arg1: types::object::LocalObject| { + let value = arg1 + .get( + ctx_scope, + &isolate_scope.create_string("foo").try_into().unwrap(), + ) + .unwrap(); + let string = String::try_from(value).unwrap(); + assert_eq!(&string, "bar"); + Result::, String>::Ok(None) } ), ) @@ -641,7 +707,7 @@ mod json_path_tests { new_native_function!(|_isolate, _ctx_scope, arg1: i64, arg2: i64| { assert_eq!(arg1, 1); assert_eq!(arg2, 2); - Result::, String>::Ok(None) + Result::, String>::Ok(None) }), ) .expect_err("Did not get error when suppose to."); @@ -659,7 +725,7 @@ mod json_path_tests { new_native_function!(|_isolate, _ctx_scope, arg1: i64, arg2: i64| { assert_eq!(arg1, 1); assert_eq!(arg2, 2); - Result::, String>::Ok(None) + Result::, String>::Ok(None) }), ) .expect_err("Did not get error when suppose to."); @@ -682,7 +748,7 @@ mod json_path_tests { assert_eq!(arg1, 1); assert_eq!(arg2, 2); assert_eq!(arg3, None); - Result::, String>::Ok(None) + Result::, String>::Ok(None) }), ) .expect_err("Did not get error when suppose to."); @@ -705,7 +771,7 @@ mod json_path_tests { assert_eq!(arg1, 1); assert_eq!(arg2, 2); assert_eq!(arg3, Some(2.2)); - Result::, String>::Ok(None) + Result::, String>::Ok(None) }), ) .expect_err("Did not get error when suppose to."); @@ -725,11 +791,11 @@ mod json_path_tests { _ctx_scope, arg1: i64, arg2: i64, - arg3: Option| { + arg3: Option| { assert_eq!(arg1, 1); assert_eq!(arg2, 2); assert!(arg3.is_none()); - Result::, String>::Ok(None) + Result::, String>::Ok(None) } ), ) @@ -750,11 +816,15 @@ mod json_path_tests { _ctx_scope, arg1: i64, arg2: i64, - arg3: Option| { + arg3: Option| { assert_eq!(arg1, 1); assert_eq!(arg2, 2); - assert_eq!(arg3.unwrap().len(), 2); - Result::, String>::Ok(None) + if let Some(array) = arg3 { + assert_eq!(array.len(), 2); + } else { + unreachable!("Should have been an array."); + } + Result::, String>::Ok(None) } ), ) @@ -767,23 +837,20 @@ mod json_path_tests { #[test] fn test_native_function_macro_optional_arguments_value() { - let err = define_function_and_call( - "test(1, 'foo', [1, 2])", - "test", - new_native_function!( - |_isolate, - _ctx_scope, - arg1: i64, - arg2: i64, - arg3: Option| { - assert_eq!(arg1, 1); - assert_eq!(arg2, 2); - assert_eq!(arg3.unwrap().is_array(), true); - Result::, String>::Ok(None) - } - ), - ) - .expect_err("Did not get error when suppose to."); + let err = + define_function_and_call( + "test(1, 'foo', [1, 2])", + "test", + new_native_function!( + |_isolate, _ctx_scope, arg1: i64, arg2: i64, arg3: Option| { + assert_eq!(arg1, 1); + assert_eq!(arg2, 2); + assert!(arg3.unwrap().is_array()); + Result::, String>::Ok(None) + } + ), + ) + .expect_err("Did not get error when suppose to."); assert_eq!( err, "Can not convert value at position 1 into i64. Value is not long." @@ -795,9 +862,9 @@ mod json_path_tests { define_function_and_call( "test(1, 'foo', [1, 2])", "test", - new_native_function!(|_isolate, _ctx_scope, arg: Vec| { + new_native_function!(|_isolate, _ctx_scope, arg: Vec| { assert_eq!(arg.len(), 3); - Result::, String>::Ok(None) + Result::, String>::Ok(None) }), ) .expect("Got error on function run"); @@ -808,13 +875,11 @@ mod json_path_tests { define_function_and_call( "test(1, 'foo', [1, 2])", "test", - new_native_function!( - |_isolate, _ctx_scope, arg1: i64, arg2: Vec| { - assert_eq!(arg1, 1); - assert_eq!(arg2.len(), 2); - Result::, String>::Ok(None) - } - ), + new_native_function!(|_isolate, _ctx_scope, arg1: i64, arg2: Vec| { + assert_eq!(arg1, 1); + assert_eq!(arg2.len(), 2); + Result::, String>::Ok(None) + }), ) .expect("Got error on function run"); } @@ -827,7 +892,7 @@ mod json_path_tests { new_native_function!(|_isolate, _ctx_scope, arg1: i64, arg2: Vec| { assert_eq!(arg1, 1); assert_eq!(arg2.len(), 2); - Result::, String>::Ok(None) + Result::, String>::Ok(None) }), ) .expect_err("Did not get error when suppose to."); diff --git a/src/v8/v8_context.rs b/src/v8/context.rs similarity index 78% rename from src/v8/v8_context.rs rename to src/v8/context.rs index dc522e4..9da5bcc 100644 --- a/src/v8/v8_context.rs +++ b/src/v8/context.rs @@ -3,6 +3,7 @@ * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or * the Server Side Public License v1 (SSPLv1). */ +//! See [Context]. use crate::v8_c_raw::bindings::{ v8_ContextEnter, v8_FreeContext, v8_GetPrivateData, v8_NewContext, v8_ResetPrivateData, @@ -14,23 +15,23 @@ use std::marker::PhantomData; use std::os::raw::c_void; use std::ptr; -use crate::v8::isolate::V8Isolate; -use crate::v8::isolate_scope::V8IsolateScope; -use crate::v8::v8_context_scope::V8ContextScope; -use crate::v8::v8_object_template::V8LocalObjectTemplate; +use crate::v8::context_scope::ContextScope; +use crate::v8::isolate::Isolate; +use crate::v8::isolate_scope::IsolateScope; +use crate::v8::types::object_template::LocalObjectTemplate; /// An RAII data guard which resets the private data slot after going /// out of scope. -pub struct V8ContextDataGuard<'context, 'data, T: 'data> { +pub struct ContextDataGuard<'context, 'data, T: 'data> { /// A raw index to reset after the guard goes out of scope. index: RawIndex, /// The context in which the guard should reset the variable. - context: &'context V8Context, + context: &'context Context, _phantom_data: PhantomData<&'data T>, } -impl<'context, 'data, T: 'data> V8ContextDataGuard<'context, 'data, T> { +impl<'context, 'data, T: 'data> ContextDataGuard<'context, 'data, T> { /// Creates a new data guard with the provided index and context scope. - pub(crate) fn new>(index: I, context: &'context V8Context) -> Self { + pub(crate) fn new>(index: I, context: &'context Context) -> Self { let index = index.into(); Self { index, @@ -40,23 +41,25 @@ impl<'context, 'data, T: 'data> V8ContextDataGuard<'context, 'data, T> { } } -impl<'context, 'data, T: 'data> Drop for V8ContextDataGuard<'context, 'data, T> { +impl<'context, 'data, T: 'data> Drop for ContextDataGuard<'context, 'data, T> { fn drop(&mut self) { self.context.reset_private_data_raw(self.index); } } -pub struct V8Context { +/// A sandboxed execution context with its own set of built-in objects +/// and functions. +pub struct Context { pub(crate) inner_ctx: *mut v8_context, } -unsafe impl Sync for V8Context {} -unsafe impl Send for V8Context {} +unsafe impl Sync for Context {} +unsafe impl Send for Context {} -impl V8Context { - pub(crate) fn new(isolate: &V8Isolate, globals: Option<&V8LocalObjectTemplate>) -> Self { +impl Context { + pub(crate) fn new(isolate: &Isolate, globals: Option<&LocalObjectTemplate>) -> Self { let inner_ctx = match globals { - Some(g) => unsafe { v8_NewContext(isolate.inner_isolate, g.inner_obj) }, + Some(g) => unsafe { v8_NewContext(isolate.inner_isolate, g.0.inner_val) }, None => unsafe { v8_NewContext(isolate.inner_isolate, ptr::null_mut()) }, }; Self { inner_ctx } @@ -69,10 +72,10 @@ impl V8Context { #[must_use] pub fn enter<'isolate_scope, 'isolate>( &self, - isolate_scope: &'isolate_scope V8IsolateScope<'isolate>, - ) -> V8ContextScope<'isolate_scope, 'isolate> { + isolate_scope: &'isolate_scope IsolateScope<'isolate>, + ) -> ContextScope<'isolate_scope, 'isolate> { let inner_ctx_ref = unsafe { v8_ContextEnter(self.inner_ctx) }; - V8ContextScope { + ContextScope { inner_ctx_ref, exit_on_drop: true, isolate_scope, @@ -94,10 +97,10 @@ impl V8Context { &'context self, index: I, data: &'data T, - ) -> V8ContextDataGuard<'context, 'data, T> { + ) -> ContextDataGuard<'context, 'data, T> { let index = index.into(); self.set_private_data_raw(index, data); - V8ContextDataGuard::new(index, self) + ContextDataGuard::new(index, self) } /// Resets a private data on the context considering the index as @@ -130,7 +133,7 @@ impl V8Context { } } -impl Drop for V8Context { +impl Drop for Context { fn drop(&mut self) { unsafe { v8_FreeContext(self.inner_ctx) } } diff --git a/src/v8/v8_context_scope.rs b/src/v8/context_scope.rs similarity index 56% rename from src/v8/v8_context_scope.rs rename to src/v8/context_scope.rs index 37c145e..3c7897d 100644 --- a/src/v8/v8_context_scope.rs +++ b/src/v8/context_scope.rs @@ -3,6 +3,7 @@ * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or * the Server Side Public License v1 (SSPLv1). */ +//! See [ContextScope]. use crate::v8_c_raw::bindings::v8_SetPrivateDataOnCtxRef; use crate::v8_c_raw::bindings::{ @@ -14,35 +15,39 @@ use crate::{RawIndex, UserIndex}; use std::marker::PhantomData; use std::os::raw::c_void; +use std::ptr::NonNull; -use crate::v8::isolate_scope::V8IsolateScope; -use crate::v8::v8_module::V8LocalModule; -use crate::v8::v8_native_function::V8LocalNativeFunction; -use crate::v8::v8_native_function_template::free_pd; -use crate::v8::v8_native_function_template::native_basic_function; -use crate::v8::v8_native_function_template::V8LocalNativeFunctionArgs; -use crate::v8::v8_object::V8LocalObject; -use crate::v8::v8_resolver::V8LocalResolver; -use crate::v8::v8_script::V8LocalScript; -use crate::v8::v8_string::V8LocalString; -use crate::v8::v8_value::V8LocalValue; +use crate::v8::isolate_scope::IsolateScope; +use crate::v8::types::module::UninitialisedLocalModule; +use crate::v8::types::native_function::LocalNativeFunction; +use crate::v8::types::native_function_template::free_pd; +use crate::v8::types::native_function_template::native_basic_function; +use crate::v8::types::native_function_template::LocalNativeFunctionArgs; +use crate::v8::types::object::LocalObject; +use crate::v8::types::resolver::LocalPromiseResolver; +use crate::v8::types::script::LocalScript; +use crate::v8::types::string::LocalString; +use crate::v8::types::ScopedValue; + +use super::types::any::LocalValueAny; +use super::types::Value; /// An RAII data guard which resets the private data slot after going /// out of scope. -pub struct V8ContextScopeDataGuard<'context_scope, 'data, 'isolate_scope, 'isolate, T: 'data> { +pub struct ContextScopeDataGuard<'context_scope, 'data, 'isolate_scope, 'isolate, T: 'data> { /// Raw Index to reset after the guard goes out of scope. index: RawIndex, /// The context scope in which the guard should reset the variable. - context_scope: &'context_scope V8ContextScope<'isolate_scope, 'isolate>, + context_scope: &'context_scope ContextScope<'isolate_scope, 'isolate>, _phantom_data: PhantomData<&'data T>, } impl<'context_scope, 'data, 'isolate_scope, 'isolate, T: 'data> - V8ContextScopeDataGuard<'context_scope, 'data, 'isolate_scope, 'isolate, T> + ContextScopeDataGuard<'context_scope, 'data, 'isolate_scope, 'isolate, T> { /// Creates a new data guard with the provided index and context scope. pub(crate) fn new>( index: I, - context_scope: &'context_scope V8ContextScope<'isolate_scope, 'isolate>, + context_scope: &'context_scope ContextScope<'isolate_scope, 'isolate>, ) -> Self { let index = index.into(); Self { @@ -54,66 +59,63 @@ impl<'context_scope, 'data, 'isolate_scope, 'isolate, T: 'data> } impl<'context_scope, 'data, 'isolate_scope, 'isolate, T: 'data> Drop - for V8ContextScopeDataGuard<'context_scope, 'data, 'isolate_scope, 'isolate, T> + for ContextScopeDataGuard<'context_scope, 'data, 'isolate_scope, 'isolate, T> { fn drop(&mut self) { self.context_scope.reset_private_data_raw(self.index); } } -pub struct V8ContextScope<'isolate_scope, 'isolate> { +/// A lifetime guard for [super::context::Context], which sets the +/// execution context for all operations executed within a local scope. +pub struct ContextScope<'isolate_scope, 'isolate> { pub(crate) inner_ctx_ref: *mut v8_context_ref, pub(crate) exit_on_drop: bool, - pub(crate) isolate_scope: &'isolate_scope V8IsolateScope<'isolate>, + pub(crate) isolate_scope: &'isolate_scope IsolateScope<'isolate>, } -impl<'isolate_scope, 'isolate> V8ContextScope<'isolate_scope, 'isolate> { +impl<'isolate_scope, 'isolate> ContextScope<'isolate_scope, 'isolate> { /// Compile the given code into a script object. - #[must_use] - pub fn compile(&self, s: &V8LocalString) -> Option> { - let inner_script = unsafe { v8_Compile(self.inner_ctx_ref, s.inner_string) }; - if inner_script.is_null() { - None - } else { - Some(V8LocalScript { - inner_script, + pub fn compile(&self, s: &LocalString) -> Option> { + NonNull::new(unsafe { v8_Compile(self.inner_ctx_ref, s.0.inner_val) }).map(|ptr| { + LocalScript(ScopedValue { + inner_val: ptr.as_ptr(), isolate_scope: self.isolate_scope, }) - } + }) } - #[must_use] - pub fn get_globals(&self) -> V8LocalObject<'isolate_scope, 'isolate> { - let inner_obj = unsafe { v8_ContextRefGetGlobals(self.inner_ctx_ref) }; - V8LocalObject { - inner_obj, + /// Returns all the global variables attached to an object. + pub fn get_globals(&self) -> LocalObject<'isolate_scope, 'isolate> { + let inner_val = unsafe { v8_ContextRefGetGlobals(self.inner_ctx_ref) }; + LocalObject(ScopedValue { + inner_val, isolate_scope: self.isolate_scope, - } + }) } /// Compile the given code as a module. - #[must_use] pub fn compile_as_module( &self, - name: &V8LocalString, - code: &V8LocalString, + name: &LocalString, + code: &LocalString, is_module: bool, - ) -> Option> { - let inner_module = unsafe { + ) -> Option> { + let inner_val = unsafe { v8_CompileAsModule( self.inner_ctx_ref, - name.inner_string, - code.inner_string, + name.0.inner_val, + code.0.inner_val, i32::from(is_module), ) }; - if inner_module.is_null() { + if inner_val.is_null() { None } else { - Some(V8LocalModule { - inner_module, + Some(UninitialisedLocalModule(ScopedValue { + inner_val, isolate_scope: self.isolate_scope, - }) + })) } } @@ -141,14 +143,12 @@ impl<'isolate_scope, 'isolate> V8ContextScope<'isolate_scope, 'isolate> { } /// Return the private data that was set on the context - #[must_use] pub fn get_private_data>(&self, index: I) -> Option<&T> { let index = index.into(); self.get_private_data_raw(index) } /// Return the private data that was set on the context as a mut reference - #[must_use] pub fn get_private_data_mut>(&self, index: I) -> Option<&mut T> { let index = index.into(); self.get_private_data_mut_raw(index) @@ -169,74 +169,80 @@ impl<'isolate_scope, 'isolate> V8ContextScope<'isolate_scope, 'isolate> { /// Sets the private data at the specified index (V8 data slot). /// Returns an RAII guard that takes care of resetting the data /// at the specified index. - #[must_use] pub fn set_private_data<'context_scope, 'data, T, I: Into>( &'context_scope self, index: I, data: &'data T, - ) -> V8ContextScopeDataGuard<'context_scope, 'data, 'isolate_scope, 'isolate, T> { + ) -> ContextScopeDataGuard<'context_scope, 'data, 'isolate_scope, 'isolate, T> { let index = index.into(); self.set_private_data_raw(index, data); - V8ContextScopeDataGuard::new(index, self) + ContextScopeDataGuard::new(index, self) } + /// Resets the private data at the provided slot. pub fn reset_private_data>(&self, index: I) { let index = index.into(); self.reset_private_data_raw(index) } /// Create a new resolver object - #[must_use] - pub fn new_resolver(&self) -> V8LocalResolver<'isolate_scope, 'isolate> { - let inner_resolver = unsafe { v8_NewResolver(self.inner_ctx_ref) }; - V8LocalResolver { - inner_resolver, + pub fn create_resolver(&self) -> LocalPromiseResolver<'isolate_scope, 'isolate> { + let inner_val = unsafe { v8_NewResolver(self.inner_ctx_ref) }; + LocalPromiseResolver(ScopedValue { + inner_val, isolate_scope: self.isolate_scope, - } + }) } - #[must_use] - pub fn new_object_from_json( + /// Creates a new JavaScript object from the passed JavaScript + /// string object. The string should contain the object structure in + /// the JavaScript Object Notation (JSON). + pub fn create_object_from_json( &self, - val: &V8LocalString, - ) -> Option> { - let inner_val = unsafe { v8_NewObjectFromJsonString(self.inner_ctx_ref, val.inner_string) }; + val: &LocalString, + ) -> Option> { + let inner_val = unsafe { v8_NewObjectFromJsonString(self.inner_ctx_ref, val.0.inner_val) }; if inner_val.is_null() { return None; } - Some(V8LocalValue { - inner_val, - isolate_scope: self.isolate_scope, - }) + Some( + LocalValueAny(ScopedValue { + inner_val, + isolate_scope: self.isolate_scope, + }) + .into(), + ) } - #[must_use] + /// Creates a JavaScript string for the provided JavaScript object + /// containing its representation in the JavaScript Object Notation + /// (JSON). pub fn json_stringify( &self, - val: &V8LocalValue, - ) -> Option> { - let inner_string = unsafe { v8_JsonStringify(self.inner_ctx_ref, val.inner_val) }; - if inner_string.is_null() { + val: &LocalValueAny, + ) -> Option> { + let inner_val = unsafe { v8_JsonStringify(self.inner_ctx_ref, val.0.inner_val) }; + if inner_val.is_null() { return None; } - Some(V8LocalString { - inner_string, + Some(LocalString(ScopedValue { + inner_val, isolate_scope: self.isolate_scope, - }) + })) } - #[must_use] - pub fn new_native_function< + /// Creates a function. See [LocalNativeFunction]. + pub fn create_native_function< T: for<'d, 'c> Fn( - &V8LocalNativeFunctionArgs<'d, 'c>, - &'d V8IsolateScope<'c>, - &V8ContextScope<'d, 'c>, - ) -> Option>, + &LocalNativeFunctionArgs<'d, 'c>, + &'d IsolateScope<'c>, + &ContextScope<'d, 'c>, + ) -> Option>, >( &self, func: T, - ) -> V8LocalNativeFunction<'isolate_scope, 'isolate> { - let inner_func = unsafe { + ) -> LocalNativeFunction<'isolate_scope, 'isolate> { + let inner_val = unsafe { v8_NewNativeFunction( self.inner_ctx_ref, Some(native_basic_function::), @@ -244,14 +250,14 @@ impl<'isolate_scope, 'isolate> V8ContextScope<'isolate_scope, 'isolate> { Some(free_pd::), ) }; - V8LocalNativeFunction { - inner_func, + LocalNativeFunction(ScopedValue { + inner_val, isolate_scope: self.isolate_scope, - } + }) } } -impl<'isolate_scope, 'isolate> Drop for V8ContextScope<'isolate_scope, 'isolate> { +impl<'isolate_scope, 'isolate> Drop for ContextScope<'isolate_scope, 'isolate> { fn drop(&mut self) { if self.exit_on_drop { unsafe { v8_ExitContextRef(self.inner_ctx_ref) } diff --git a/src/v8/isolate.rs b/src/v8/isolate.rs index 0ebe8b4..cdf74cb 100644 --- a/src/v8/isolate.rs +++ b/src/v8/isolate.rs @@ -14,33 +14,34 @@ use crate::v8_c_raw::bindings::{ use std::os::raw::c_void; -use crate::v8::isolate_scope::V8IsolateScope; +use crate::v8::isolate_scope::IsolateScope; use std::ffi::CStr; use std::os::raw::{c_char, c_int}; /// An isolate rust wrapper object. /// The isolate will not be automatically freed. /// In order to free an isolate, one must call [`V8Isolate::free_isolate`]. -pub struct V8Isolate { +#[derive(Debug)] +pub struct Isolate { pub(crate) inner_isolate: *mut v8_isolate, pub(crate) no_release: bool, } -unsafe impl Sync for V8Isolate {} -unsafe impl Send for V8Isolate {} +unsafe impl Sync for Isolate {} +unsafe impl Send for Isolate {} -pub(crate) extern "C" fn interrupt_callback( +pub(crate) extern "C" fn interrupt_callback( inner_isolate: *mut v8_isolate, data: *mut ::std::os::raw::c_void, ) { let func = unsafe { &*(data.cast::()) }; - func(&V8Isolate { + func(&Isolate { inner_isolate, no_release: true, }); } -impl Default for V8Isolate { +impl Default for Isolate { fn default() -> Self { Self::new() } @@ -77,7 +78,7 @@ extern "C" fn near_oom_callback_free_pd usize>(data: *mut } } -impl V8Isolate { +impl Isolate { /// Create a new v8 isolate with default heap size (up to 1G). #[must_use] pub fn new() -> Self { @@ -110,13 +111,13 @@ impl V8Isolate { } /// Enter the isolate for code invocation. - /// Return an `V8IsolateScope` object, when the returned + /// Return an [`IsolateScope`] object, when the returned /// object is destroy the code will exit the isolate. /// /// An isolate must be entered before running any JS code. #[must_use] - pub fn enter(&self) -> V8IsolateScope { - V8IsolateScope::new(self) + pub fn enter(&self) -> IsolateScope { + IsolateScope::new(self) } /// Sets an idle notification that the embedder is idle for longer @@ -232,7 +233,7 @@ impl V8Isolate { } } -impl Drop for V8Isolate { +impl Drop for Isolate { fn drop(&mut self) { if !self.no_release { unsafe { v8_FreeIsolate(self.inner_isolate) } diff --git a/src/v8/isolate_scope.rs b/src/v8/isolate_scope.rs index ce08569..5133966 100644 --- a/src/v8/isolate_scope.rs +++ b/src/v8/isolate_scope.rs @@ -3,49 +3,39 @@ * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or * the Server Side Public License v1 (SSPLv1). */ +//! See [IsolateScope]. use crate::v8_c_raw::bindings::{ - v8_FreeHandlersScope, v8_IsolateEnter, v8_IsolateExit, v8_IsolateRaiseException, v8_NewArray, - v8_NewArrayBuffer, v8_NewBool, v8_NewExternalData, v8_NewHandlersScope, - v8_NewNativeFunctionTemplate, v8_NewNull, v8_NewObject, v8_NewObjectTemplate, v8_NewSet, - v8_NewString, v8_NewTryCatch, v8_NewUnlocker, v8_StringToValue, v8_ValueFromDouble, - v8_ValueFromLong, v8_handlers_scope, v8_isolate_scope, v8_local_value, + v8_FreeHandlersScope, v8_IsolateEnter, v8_IsolateExit, v8_IsolateRaiseException, + v8_NewHandlersScope, v8_handlers_scope, v8_isolate_scope, }; -use crate::v8::isolate::V8Isolate; -use crate::v8::try_catch::V8TryCatch; -use crate::v8::v8_array::V8LocalArray; -use crate::v8::v8_array_buffer::V8LocalArrayBuffer; -use crate::v8::v8_context::V8Context; -use crate::v8::v8_context_scope::V8ContextScope; -use crate::v8::v8_external_data::V8LocalExternalData; -use crate::v8::v8_native_function_template::{ - free_pd, native_basic_function, V8LocalNativeFunctionArgs, V8LocalNativeFunctionTemplate, -}; -use crate::v8::v8_object::V8LocalObject; -use crate::v8::v8_object_template::V8LocalObjectTemplate; -use crate::v8::v8_set::V8LocalSet; -use crate::v8::v8_string::V8LocalString; -use crate::v8::v8_unlocker::V8Unlocker; -use crate::v8::v8_value::V8LocalValue; - -use std::os::raw::{c_char, c_void}; - -pub struct V8IsolateScope<'isolate> { - pub(crate) isolate: &'isolate V8Isolate, +use crate::v8::context::Context; +use crate::v8::isolate::Isolate; +use crate::v8::types::object_template::LocalObjectTemplate; +use crate::v8::types::string::LocalString; + +use super::context_scope::ContextScope; +use super::types::any::LocalValueAny; +use super::types::native_function_template::LocalNativeFunctionArgs; +use super::types::Value; + +/// Isolate scope is a lifetime guard for the [Isolate]. +/// It allows working with the bound [Isolate] object and correctly +/// drop the data associated with the [Isolate] when it goes out of +/// scope. +#[derive(Debug)] +pub struct IsolateScope<'isolate> { + pub(crate) isolate: &'isolate Isolate, inner_handlers_scope: *mut v8_handlers_scope, inner_isolate_scope: *mut v8_isolate_scope, } -extern "C" fn free_external_data(arg1: *mut ::std::os::raw::c_void) { - unsafe { Box::from_raw(arg1 as *mut T) }; -} - -impl<'isolate> V8IsolateScope<'isolate> { - pub(crate) fn new(isolate: &'isolate V8Isolate) -> V8IsolateScope<'isolate> { +impl<'isolate> IsolateScope<'isolate> { + pub(crate) fn new(isolate: &'isolate Isolate) -> IsolateScope<'isolate> { let inner_isolate_scope = unsafe { v8_IsolateEnter(isolate.inner_isolate) }; let inner_handlers_scope = unsafe { v8_NewHandlersScope(isolate.inner_isolate) }; - V8IsolateScope { + IsolateScope { isolate, inner_handlers_scope, inner_isolate_scope, @@ -54,232 +44,152 @@ impl<'isolate> V8IsolateScope<'isolate> { /// Creating a new context for JS code invocation. #[must_use] - pub fn new_context(&self, globals: Option<&V8LocalObjectTemplate>) -> V8Context { - V8Context::new(self.isolate, globals) + pub fn create_context(&self, globals: Option<&LocalObjectTemplate>) -> Context { + Context::new(self.isolate, globals) } /// Raise an exception with the given local generic value. - pub fn raise_exception(&self, exception: V8LocalValue) { - unsafe { v8_IsolateRaiseException(self.isolate.inner_isolate, exception.inner_val) }; + pub fn raise_exception(&self, exception: LocalValueAny) { + unsafe { v8_IsolateRaiseException(self.isolate.inner_isolate, exception.0.inner_val) }; } /// Same as `raise_exception` but raise exception with the given massage. pub fn raise_exception_str(&self, msg: &str) { - let inner_string = unsafe { - v8_NewString( - self.isolate.inner_isolate, - msg.as_ptr().cast::(), - msg.len(), - ) - }; - let inner_val = unsafe { v8_StringToValue(inner_string) }; - unsafe { v8_IsolateRaiseException(self.isolate.inner_isolate, inner_val) }; + let value = LocalValueAny::from(LocalString::new(msg, self)); + unsafe { v8_IsolateRaiseException(self.isolate.inner_isolate, value.0.inner_val) }; } - /// Return a new try catch object. The object will catch any exception that was - /// raised during the JS code invocation. + /// Returns a new try catch object. The object will catch any + /// exception that was raised during the JS code invocation. #[must_use] - pub fn new_try_catch<'isolate_scope>( + pub fn create_try_catch<'isolate_scope>( &'isolate_scope self, - ) -> V8TryCatch<'isolate_scope, 'isolate> { - let inner_trycatch = unsafe { v8_NewTryCatch(self.isolate.inner_isolate) }; - V8TryCatch { - inner_trycatch, - isolate_scope: self, - } + ) -> Value<'isolate_scope, 'isolate> { + Value::new_try_catch(self) } - /// Create a new string object. + /// Create a new string object. See + /// [crate::v8::types::string::LocalString]. #[must_use] - pub fn new_string<'isolate_scope>( + pub fn create_string<'isolate_scope>( &'isolate_scope self, s: &str, - ) -> V8LocalString<'isolate_scope, 'isolate> { - let inner_string = unsafe { - v8_NewString( - self.isolate.inner_isolate, - s.as_ptr().cast::(), - s.len(), - ) - }; - V8LocalString { - inner_string, - isolate_scope: self, - } + ) -> Value<'isolate_scope, 'isolate> { + Value::from_str(s, self) } - /// Create a new string object. + /// Create a new array object. See + /// [crate::v8::types::array::LocalArray]. #[must_use] - pub fn new_array<'isolate_scope>( + pub fn create_array<'isolate_scope>( &'isolate_scope self, - values: &[&V8LocalValue], - ) -> V8LocalArray<'isolate_scope, 'isolate> { - let args = values - .iter() - .map(|v| v.inner_val) - .collect::>(); - let ptr = args.as_ptr(); - let inner_array = unsafe { v8_NewArray(self.isolate.inner_isolate, ptr, values.len()) }; - V8LocalArray { - inner_array, - isolate_scope: self, - } + values: &[&LocalValueAny], + ) -> Value<'isolate_scope, 'isolate> { + Value::from_array(values, self) } + /// Creates a new array buffer. See + /// [crate::v8::types::array_buffer::LocalArrayBuffer]. #[must_use] - pub fn new_array_buffer<'isolate_scope>( + pub fn create_array_buffer<'isolate_scope>( &'isolate_scope self, - buff: &[u8], - ) -> V8LocalArrayBuffer<'isolate_scope, 'isolate> { - let inner_array_buffer = unsafe { - v8_NewArrayBuffer( - self.isolate.inner_isolate, - buff.as_ptr() as *const c_char, - buff.len(), - ) - }; - V8LocalArrayBuffer { - inner_array_buffer, - isolate_scope: self, - } + bytes: &[u8], + ) -> Value<'isolate_scope, 'isolate> { + Value::from_array_buffer(bytes, self) } + /// Creates a new object. See + /// [crate::v8::types::object::LocalObject]. #[must_use] - pub fn new_object<'isolate_scope>( - &'isolate_scope self, - ) -> V8LocalObject<'isolate_scope, 'isolate> { - let inner_obj = unsafe { v8_NewObject(self.isolate.inner_isolate) }; - V8LocalObject { - inner_obj, - isolate_scope: self, - } + pub fn create_object<'isolate_scope>(&'isolate_scope self) -> Value<'isolate_scope, 'isolate> { + Value::new_object(self) } + /// Creates a new external data object. See + /// [crate::v8::types::external_data::LocalExternalData]. #[must_use] - pub fn new_external_data<'isolate_scope, T>( + pub fn create_external_data<'isolate_scope, T>( &'isolate_scope self, data: T, - ) -> V8LocalExternalData<'isolate_scope, 'isolate> { - let data = Box::into_raw(Box::new(data)); - let inner_ext = unsafe { - v8_NewExternalData( - self.isolate.inner_isolate, - data as *mut c_void, - Some(free_external_data::), - ) - }; - V8LocalExternalData { - inner_ext, - isolate_scope: self, - } + ) -> Value<'isolate_scope, 'isolate> { + Value::from_external_data(data, self) } + /// Creates a new set object. See + /// [crate::v8::types::set::LocalSet]. #[must_use] - pub fn new_set<'isolate_scope>(&'isolate_scope self) -> V8LocalSet<'isolate_scope, 'isolate> { - let inner_set = unsafe { v8_NewSet(self.isolate.inner_isolate) }; - V8LocalSet { - inner_set, - isolate_scope: self, - } + pub fn create_set<'isolate_scope>(&'isolate_scope self) -> Value<'isolate_scope, 'isolate> { + Value::new_set(self) } + /// Creates a new boolean object. See + /// [crate::v8::types::LocalValueBoolean]. #[must_use] - pub fn new_bool<'isolate_scope>( + pub fn create_bool<'isolate_scope>( &'isolate_scope self, val: bool, - ) -> V8LocalValue<'isolate_scope, 'isolate> { - let inner_val = unsafe { v8_NewBool(self.isolate.inner_isolate, val as i32) }; - V8LocalValue { - inner_val, - isolate_scope: self, - } + ) -> Value<'isolate_scope, 'isolate> { + Value::from_bool(val, self) } - pub fn new_long<'isolate_scope>( + /// Creates a new `BigInt` object. See + /// [crate::v8::types::LocalValueBigInteger]. + pub fn create_long<'isolate_scope>( &'isolate_scope self, val: i64, - ) -> V8LocalValue<'isolate_scope, 'isolate> { - let inner_val = unsafe { v8_ValueFromLong(self.isolate.inner_isolate, val) }; - V8LocalValue { - inner_val, - isolate_scope: self, - } + ) -> Value<'isolate_scope, 'isolate> { + Value::from_i64(val, self) } - pub fn new_double<'isolate_scope>( + /// Creates a new `Number` primitive. See + /// [crate::v8::types::LocalValueNumber]. + pub fn create_double<'isolate_scope>( &'isolate_scope self, val: f64, - ) -> V8LocalValue<'isolate_scope, 'isolate> { - let inner_val = unsafe { v8_ValueFromDouble(self.isolate.inner_isolate, val) }; - V8LocalValue { - inner_val, - isolate_scope: self, - } + ) -> Value<'isolate_scope, 'isolate> { + Value::from_f64(val, self) } - pub fn new_null<'isolate_scope>( - &'isolate_scope self, - ) -> V8LocalValue<'isolate_scope, 'isolate> { - let inner_val = unsafe { v8_NewNull(self.isolate.inner_isolate) }; - V8LocalValue { - inner_val, - isolate_scope: self, - } + /// Creates a new `null` object. See + /// [crate::v8::types::LocalValueNull]. + pub fn create_null<'isolate_scope>(&'isolate_scope self) -> Value<'isolate_scope, 'isolate> { + Value::new_null(self) } /// Create a new JS object template. #[must_use] - pub fn new_object_template<'isolate_scope>( + pub fn create_object_template<'isolate_scope>( &'isolate_scope self, - ) -> V8LocalObjectTemplate<'isolate_scope, 'isolate> { - let inner_obj = unsafe { v8_NewObjectTemplate(self.isolate.inner_isolate) }; - V8LocalObjectTemplate { - inner_obj, - isolate_scope: self, - } + ) -> Value<'isolate_scope, 'isolate> { + Value::new_object_template(self) } /// Create a new native function template. - pub fn new_native_function_template< + pub fn create_native_function_template< 'isolate_scope, - T: for<'d, 'e> Fn( - &V8LocalNativeFunctionArgs<'d, 'e>, - &'d V8IsolateScope<'e>, - &V8ContextScope<'d, 'e>, - ) -> Option>, + T: for<'d, 'c> Fn( + &LocalNativeFunctionArgs<'d, 'c>, + &'d IsolateScope<'c>, + &ContextScope<'d, 'c>, + ) -> Option>, >( &'isolate_scope self, - func: T, - ) -> V8LocalNativeFunctionTemplate<'isolate_scope, 'isolate> { - let inner_func = unsafe { - v8_NewNativeFunctionTemplate( - self.isolate.inner_isolate, - Some(native_basic_function::), - Box::into_raw(Box::new(func)).cast::(), - Some(free_pd::), - ) - }; - V8LocalNativeFunctionTemplate { - inner_func, - isolate_scope: self, - } + function: T, + ) -> Value<'isolate_scope, 'isolate> { + Value::new_native_function_template(function, self) } /// Create a new unlocker object that releases the isolate global lock. /// The lock will be re-aquire when the unlocker will be released. #[must_use] - pub fn new_unlocker<'isolate_scope>( + pub fn create_unlocker<'isolate_scope>( &'isolate_scope self, - ) -> V8Unlocker<'isolate_scope, 'isolate> { - let inner_unlocker = unsafe { v8_NewUnlocker(self.isolate.inner_isolate) }; - V8Unlocker { - inner_unlocker, - _isolate_scope: self, - } + ) -> Value<'isolate_scope, 'isolate> { + Value::new_unlocker(self) } } -impl<'isolate> Drop for V8IsolateScope<'isolate> { +impl<'isolate> Drop for IsolateScope<'isolate> { fn drop(&mut self) { unsafe { v8_FreeHandlersScope(self.inner_handlers_scope); diff --git a/src/v8/mod.rs b/src/v8/mod.rs index 775244d..3f8a000 100644 --- a/src/v8/mod.rs +++ b/src/v8/mod.rs @@ -9,36 +9,29 @@ use crate::v8_c_raw::bindings::{v8_Dispose, v8_Initialize, v8_Version}; use std::ffi::CStr; use std::ptr; +pub mod context; +pub mod context_scope; pub mod isolate; pub mod isolate_scope; -pub mod try_catch; -pub mod v8_array; -pub mod v8_array_buffer; -pub mod v8_context; -pub mod v8_context_scope; -pub mod v8_external_data; -pub mod v8_module; -pub mod v8_native_function; -pub mod v8_native_function_template; -pub mod v8_object; -pub mod v8_object_template; -pub mod v8_promise; -pub mod v8_resolver; -pub mod v8_script; -pub mod v8_set; -pub mod v8_string; -pub mod v8_unlocker; -pub mod v8_utf8; -pub mod v8_value; +pub mod types; pub(crate) type FatalErrorCallback = dyn Fn(&str, &str); pub(crate) type OutOfMemoryErrorCallback = dyn Fn(&str, bool); pub(crate) static mut FATAL_ERROR_CALLBACK: Option> = None; pub(crate) static mut OOM_ERROR_CALLBACK: Option> = None; +/// A value-missing-aware conversion for types. If a value passed to the +/// [OptionalTryFrom::optional_try_from] method may be considered +/// absent, the [Option::None] is returned instead of an error; errors +/// are only returned when there **is** a value (it is present) but the +/// conversion itself fails. pub trait OptionalTryFrom: Sized { + /// The error type for the conversion failure. type Error; + /// Returns an [Option] of the value indicating the presence or + /// absence of the value, and [Result::Err] in case there was an + /// error during the conversion. fn optional_try_from(value: T) -> Result, Self::Error>; } diff --git a/src/v8/try_catch.rs b/src/v8/try_catch.rs deleted file mode 100644 index 45b2c76..0000000 --- a/src/v8/try_catch.rs +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright Redis Ltd. 2022 - present - * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or - * the Server Side Public License v1 (SSPLv1). - */ - -use crate::v8_c_raw::bindings::{ - v8_FreeTryCatch, v8_TryCatchGetException, v8_TryCatchGetTrace, v8_TryCatchHasTerminated, - v8_trycatch, -}; - -use crate::v8::isolate_scope::V8IsolateScope; -use crate::v8::v8_context_scope::V8ContextScope; -use crate::v8::v8_value::V8LocalValue; - -/// An object that responsible to catch any exception which raised -/// during the JS code invocation. -pub struct V8TryCatch<'isolate_scope, 'isolate> { - pub(crate) inner_trycatch: *mut v8_trycatch, - pub(crate) isolate_scope: &'isolate_scope V8IsolateScope<'isolate>, -} - -impl<'isolate_scope, 'isolate> V8TryCatch<'isolate_scope, 'isolate> { - /// Return the exception that was raise during the JS code invocation. - #[must_use] - pub fn get_exception(&self) -> V8LocalValue<'isolate_scope, 'isolate> { - let inner_val = unsafe { v8_TryCatchGetException(self.inner_trycatch) }; - assert!(!inner_val.is_null()); - V8LocalValue { - inner_val, - isolate_scope: self.isolate_scope, - } - } - - /// Return the trace of the catch exception, the function returns Option because trace is not always provided. - #[must_use] - pub fn get_trace( - &self, - ctx_scope: &V8ContextScope, - ) -> Option> { - let inner_val = - unsafe { v8_TryCatchGetTrace(self.inner_trycatch, ctx_scope.inner_ctx_ref) }; - if inner_val.is_null() { - return None; - } - Some(V8LocalValue { - inner_val, - isolate_scope: self.isolate_scope, - }) - } - - #[must_use] - pub fn has_terminated(&self) -> bool { - let res = unsafe { v8_TryCatchHasTerminated(self.inner_trycatch) }; - res > 0 - } -} - -impl<'isolate_scope, 'isolate> Drop for V8TryCatch<'isolate_scope, 'isolate> { - fn drop(&mut self) { - unsafe { v8_FreeTryCatch(self.inner_trycatch) } - } -} diff --git a/src/v8/types/any.rs b/src/v8/types/any.rs new file mode 100644 index 0000000..b6ce671 --- /dev/null +++ b/src/v8/types/any.rs @@ -0,0 +1,555 @@ +/* + * Copyright Redis Ltd. 2022 - present + * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or + * the Server Side Public License v1 (SSPLv1). + */ +//! This is an abstraction over a scoped V8 value type. +//! As this is a fully abstract class in V8, the objects it can hold +//! can be of any javascript type. Hence it is generic. + +use std::ptr::NonNull; + +use crate::{ + v8::context_scope::ContextScope, + v8_c_raw::bindings::{ + v8_FreeValue, v8_FunctionCall, v8_GetBigInt, v8_GetBool, v8_GetNumber, v8_PersistValue, + v8_ValueAsArray, v8_ValueAsArrayBuffer, v8_ValueAsExternalData, v8_ValueAsObject, + v8_ValueAsPromise, v8_ValueAsResolver, v8_ValueAsSet, v8_ValueAsString, v8_ValueIsArray, + v8_ValueIsArrayBuffer, v8_ValueIsAsyncFunction, v8_ValueIsBigInt, v8_ValueIsBool, + v8_ValueIsExternalData, v8_ValueIsFunction, v8_ValueIsNull, v8_ValueIsNumber, + v8_ValueIsObject, v8_ValueIsPromise, v8_ValueIsSet, v8_ValueIsString, + v8_ValueIsStringObject, v8_local_value, + }, +}; + +use super::{ + array::LocalArray, array_buffer::LocalArrayBuffer, external_data::LocalExternalData, + object::LocalObject, persistent::PersistValue, promise::LocalPromise, + resolver::LocalPromiseResolver, set::LocalSet, string::LocalString, utf8::LocalUtf8, + ScopedValue, +}; + +/// A type the objects of [LocalValueAny] can hold. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub enum Type { + /// The `BigInt` type in JavaScript. + /// BigInt values represent numeric values which are too large to be + /// represented by the number + BigInteger, + /// The `Number` type in JavaScript. + /// All primitive numbers in JavaScript are of this (`Number`) type, + /// even "Integer" literals like `5` is a floating-point value of + /// type `Number`. + /// + /// # Example + /// + /// ```javascript + /// 255; // two-hundred and fifty-five + /// 255.0; // same number + /// 255 === 255.0; // true + /// 255 === 0xff; // true (hexadecimal notation) + /// 255 === 0b11111111; // true (binary notation) + /// 255 === 0.255e+3; // true (decimal exponential notation) + /// ``` + /// + /// See more at + /// . + Number, + /// A boolean JavaScript object (not primitive!). + /// + /// # Example + /// + /// ```javascript + /// const x = new Boolean(false); + /// if (x) { + /// // this code is executed + /// } + /// // However: + /// const x = false; + /// if (x) { + /// // this code is not executed + /// } + /// ``` + Boolean, + /// A JavaScript's `null` value. + Null, + /// The Set object lets you store unique values of any type, + /// whether primitive values or object references. + /// + /// # Example + /// + /// ```javascript + /// const mySet1 = new Set(); + /// + /// mySet1.add(1); // Set(1) { 1 } + /// ``` + Set, + /// A foreign, non-JavaScript object which can be worked with in + /// JavaScript. It doesn't have methods, but can be passed and + /// received. Usually, for such objects an API is provided to work + /// with those. An example may be a C++ object, for which a pointer + /// is provided to JavaScript as [Type::ExternalData]. + ExternalData, + /// A JavaScript object. See more at + /// . + Object, + /// Resolver is an object which can resolve or reject promises. + /// See more at + /// . + Resolver, + /// The Promise object represents the eventual completion + /// (or failure) of an asynchronous operation and its resulting + /// value. + /// + /// # Example + /// + /// ```javascript + /// new Promise((resolveOuter) => { + /// resolveOuter( + /// new Promise((resolveInner) => { + /// setTimeout(resolveInner, 1000); + /// }), + /// ); + /// }); + /// ``` + Promise, + /// A JavaScript function. Every function is actually an object of + /// type `Function`. + /// + /// # [Example](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function) + /// + /// ```javascript + /// // Create a global property with `var` + /// var x = 10; + /// + /// function createFunction1() { + /// const x = 20; + /// return new Function("return x;"); // this `x` refers to global `x` + /// } + /// + /// function createFunction2() { + /// const x = 20; + /// function f() { + /// return x; // this `x` refers to the local `x` above + /// } + /// return f; + /// } + /// + /// const f1 = createFunction1(); + /// console.log(f1()); // 10 + /// const f2 = createFunction2(); + /// console.log(f2()); // 20 + /// ``` + Function, + /// An asynchrous function. Every async function in JavaScript is + /// actually an AsyncFunction object. + /// + AsyncFunction, + /// A javascript array, which can be created like this: + /// ```javascript + /// const fruits = []; + /// fruits.push("banana", "apple", "peach"); + /// ``` + Array, + /// The ArrayBuffer is used to represent a generic raw binary data + /// buffer. Example: + /// ```javascript + /// const buffer = new ArrayBuffer(8); + /// const view = new Int32Array(buffer); + /// ``` + ArrayBuffer, + /// A literal string in JavaScript. For example: + /// ```javascript + /// const string1 = "A string primitive"; + /// const string2 = 'Also a string primitive'; + /// const string3 = `Yet another string primitive`; + /// ``` + String, + /// A string object. A string object is a proper JavaScript object + /// which can be created the following way: + /// ```javascript + /// const stringObject = new String("A String object"); + /// ``` + StringObject, + /// A UTF-8 encoded string. + Utf8, +} + +/// A local value for which there is no type information available. +#[repr(transparent)] +#[derive(Debug, Clone)] +pub struct LocalValueAny<'isolate_scope, 'isolate>( + pub(crate) ScopedValue<'isolate_scope, 'isolate, v8_local_value>, +); + +impl<'isolate_scope, 'isolate> LocalValueAny<'isolate_scope, 'isolate> { + /// Returns the type of the value hold if it the valid, [`None`] + /// otherwise. + pub fn get_type(&self) -> Option { + Some(if self.is_array() { + Type::Array + } else if self.is_array_buffer() { + Type::ArrayBuffer + } else if self.is_async_function() { + Type::AsyncFunction + } else if self.is_boolean() { + Type::Boolean + } else if self.is_external_data() { + Type::ExternalData + } else if self.is_function() { + Type::Function + } else if self.is_long() { + Type::BigInteger + } else if self.is_null() { + Type::Null + } else if self.is_number() { + Type::Number + } else if self.is_object() { + Type::Object + } else if self.is_promise() { + Type::Promise + } else if self.is_set() { + Type::Set + } else if self.is_string() { + Type::String + } else if self.is_string_object() { + Type::StringObject + } else { + return None; + }) + } + + /// Return string representation of the value or None on failure + #[deprecated = "Use [LocalUtf8::try_from] instead."] + pub fn into_utf8(self) -> Option> { + LocalUtf8::try_from(self).ok() + } + + /// Return true if the value is string and false otherwise. + pub fn is_string(&self) -> bool { + (unsafe { v8_ValueIsString(self.0.inner_val) } != 0) + } + + /// Convert the object into a string, applicable only if the value is string. + /// + /// # Safety + /// + /// The function doesn't perform checks for the value being actually + /// of the target type. And doesn't panic if this is not the case. + /// If a fallible conversion is preferred, use [`TryFrom`]. + /// + /// In case the target type is not checked before this function is + /// invoked and the value is not of this target type, the results + /// are unknown. + pub unsafe fn as_string(&self) -> LocalString<'isolate_scope, 'isolate> { + let inner_val = unsafe { v8_ValueAsString(self.0.inner_val) }; + LocalString(ScopedValue { + inner_val, + isolate_scope: self.0.isolate_scope, + }) + } + + /// Return true if the value is string object and false otherwise. + pub fn is_string_object(&self) -> bool { + (unsafe { v8_ValueIsStringObject(self.0.inner_val) } != 0) + } + + /// Return true if the value is string and false otherwise. + pub fn is_array(&self) -> bool { + (unsafe { v8_ValueIsArray(self.0.inner_val) } != 0) + } + + /// Convert the object into a string, applicable only if the value is string. + /// + /// # Safety + /// + /// The function doesn't perform checks for the value being actually + /// of the target type. And doesn't panic if this is not the case. + /// If a fallible conversion is preferred, use [`TryFrom`]. + /// + /// In case the target type is not checked before this function is + /// invoked and the value is not of this target type, the results + /// are unknown. + pub unsafe fn as_array(&self) -> LocalArray<'isolate_scope, 'isolate> { + let inner_val = unsafe { v8_ValueAsArray(self.0.inner_val) }; + LocalArray(ScopedValue { + inner_val, + isolate_scope: self.0.isolate_scope, + }) + } + + /// Return true if the value is string and false otherwise. + pub fn is_array_buffer(&self) -> bool { + (unsafe { v8_ValueIsArrayBuffer(self.0.inner_val) } != 0) + } + + /// Convert the object into a string, applicable only if the value is string. + /// + /// # Safety + /// + /// The function doesn't perform checks for the value being actually + /// of the target type. And doesn't panic if this is not the case. + /// If a fallible conversion is preferred, use [`TryFrom`]. + /// + /// In case the target type is not checked before this function is + /// invoked and the value is not of this target type, the results + /// are unknown. + pub unsafe fn as_array_buffer(&self) -> LocalArrayBuffer<'isolate_scope, 'isolate> { + let inner_val = unsafe { v8_ValueAsArrayBuffer(self.0.inner_val) }; + LocalArrayBuffer(ScopedValue { + inner_val, + isolate_scope: self.0.isolate_scope, + }) + } + + /// Return true if the value is null and false otherwise. + pub fn is_null(&self) -> bool { + (unsafe { v8_ValueIsNull(self.0.inner_val) } != 0) + } + + /// Return true if the value is function and false otherwise. + pub fn is_function(&self) -> bool { + (unsafe { v8_ValueIsFunction(self.0.inner_val) } != 0) + } + + /// Return true if the value is async function and false otherwise. + pub fn is_async_function(&self) -> bool { + (unsafe { v8_ValueIsAsyncFunction(self.0.inner_val) } != 0) + } + + /// Return true if the value is number and false otherwise. + pub fn is_number(&self) -> bool { + (unsafe { v8_ValueIsNumber(self.0.inner_val) } != 0) + } + + /// Returns an [f64] value. + /// + /// # Safety + /// + /// The function doesn't perform checks for the value being actually + /// of the target type. And doesn't panic if this is not the case. + /// If a fallible conversion is preferred, use [`TryFrom`]. + /// + /// In case the target type is not checked before this function is + /// invoked and the value is not of this target type, the results + /// are unknown. + pub unsafe fn get_number(&self) -> f64 { + unsafe { v8_GetNumber(self.0.inner_val) } + } + + /// Return true if the value is number and false otherwise. + pub fn is_long(&self) -> bool { + (unsafe { v8_ValueIsBigInt(self.0.inner_val) } != 0) + } + + /// Returns an [i64] value. + /// + /// # Safety + /// + /// The function doesn't perform checks for the value being actually + /// of the target type. And doesn't panic if this is not the case. + /// If a fallible conversion is preferred, use [`TryFrom`]. + /// + /// In case the target type is not checked before this function is + /// invoked and the value is not of this target type, the results + /// are unknown. + pub unsafe fn get_long(&self) -> i64 { + unsafe { v8_GetBigInt(self.0.inner_val) } + } + + /// Return true if the value is boolean and false otherwise. + pub fn is_boolean(&self) -> bool { + (unsafe { v8_ValueIsBool(self.0.inner_val) } != 0) + } + + /// Returns a [bool] value. + /// + /// # Safety + /// + /// The function doesn't perform checks for the value being actually + /// of the target type. And doesn't panic if this is not the case. + /// If a fallible conversion is preferred, use [`TryFrom`]. + /// + /// In case the target type is not checked before this function is + /// invoked and the value is not of this target type, the results + /// are unknown. + pub unsafe fn get_boolean(&self) -> bool { + (unsafe { v8_GetBool(self.0.inner_val) } != 0) + } + + /// Return true if the value is promise and false otherwise. + pub fn is_promise(&self) -> bool { + (unsafe { v8_ValueIsPromise(self.0.inner_val) } != 0) + } + + /// Returns a [LocalPromise] value. + /// + /// # Safety + /// + /// The function doesn't perform checks for the value being actually + /// of the target type. And doesn't panic if this is not the case. + /// If a fallible conversion is preferred, use [`TryFrom`]. + /// + /// In case the target type is not checked before this function is + /// invoked and the value is not of this target type, the results + /// are unknown. + pub unsafe fn as_promise(&self) -> LocalPromise<'isolate_scope, 'isolate> { + let inner_val = unsafe { v8_ValueAsPromise(self.0.inner_val) }; + LocalPromise(ScopedValue { + inner_val, + isolate_scope: self.0.isolate_scope, + }) + } + + /// Returns a [LocalResolver] value. + /// + /// # Safety + /// + /// The function doesn't perform checks for the value being actually + /// of the target type. And doesn't panic if this is not the case. + /// If a fallible conversion is preferred, use [`TryFrom`]. + /// + /// In case the target type is not checked before this function is + /// invoked and the value is not of this target type, the results + /// are unknown. + pub unsafe fn as_resolver(&self) -> LocalPromiseResolver<'isolate_scope, 'isolate> { + let inner_val = unsafe { v8_ValueAsResolver(self.0.inner_val) }; + LocalPromiseResolver(ScopedValue { + inner_val, + isolate_scope: self.0.isolate_scope, + }) + } + + /// Return true if the value is object and false otherwise. + pub fn is_object(&self) -> bool { + (unsafe { v8_ValueIsObject(self.0.inner_val) } != 0) + } + + /// Returns a [LocalObject] value. + /// + /// # Safety + /// + /// The function doesn't perform checks for the value being actually + /// of the target type. And doesn't panic if this is not the case. + /// If a fallible conversion is preferred, use [`TryFrom`]. + /// + /// In case the target type is not checked before this function is + /// invoked and the value is not of this target type, the results + /// are unknown. + pub unsafe fn as_object(&self) -> LocalObject<'isolate_scope, 'isolate> { + let inner_obj = unsafe { v8_ValueAsObject(self.0.inner_val) }; + LocalObject(ScopedValue { + inner_val: inner_obj, + isolate_scope: self.0.isolate_scope, + }) + } + + /// Returns `true` if the value stored is of type `External`. If it + /// is so, this object can be converted into the [LocalExternalData]. + pub fn is_external_data(&self) -> bool { + (unsafe { v8_ValueIsExternalData(self.0.inner_val) } != 0) + } + + /// Returns a [LocalExternalData] value. + /// + /// # Safety + /// + /// The function doesn't perform checks for the value being actually + /// of the target type. And doesn't panic if this is not the case. + /// If a fallible conversion is preferred, use [`TryFrom`]. + /// + /// In case the target type is not checked before this function is + /// invoked and the value is not of this target type, the results + /// are unknown. + pub unsafe fn as_external_data(&self) -> LocalExternalData<'isolate_scope, 'isolate> { + let inner_val = unsafe { v8_ValueAsExternalData(self.0.inner_val) }; + LocalExternalData(ScopedValue { + inner_val, + isolate_scope: self.0.isolate_scope, + }) + } + + /// Return true if the value is set and false otherwise. + pub fn is_set(&self) -> bool { + (unsafe { v8_ValueIsSet(self.0.inner_val) } != 0) + } + + /// Returns a [LocalSet] value. + /// + /// # Safety + /// + /// The function doesn't perform checks for the value being actually + /// of the target type. And doesn't panic if this is not the case. + /// If a fallible conversion is preferred, use [`TryFrom`]. + /// + /// In case the target type is not checked before this function is + /// invoked and the value is not of this target type, the results + /// are unknown. + pub unsafe fn as_set(&self) -> LocalSet<'isolate_scope, 'isolate> { + let inner_val = unsafe { v8_ValueAsSet(self.0.inner_val) }; + LocalSet(ScopedValue { + inner_val, + isolate_scope: self.0.isolate_scope, + }) + } + + /// Persist the local object so it can be saved beyond the current handlers scope. + /// TODO move to `From` impl. + pub fn persist(&self) -> PersistValue { + let inner_val = unsafe { + v8_PersistValue(self.0.isolate_scope.isolate.inner_isolate, self.0.inner_val) + }; + PersistValue::from(inner_val) + } + + /// Run the value, applicable only if the value is a function or async function. + pub fn call(&self, ctx: &ContextScope, args: Option<&[&Self]>) -> Option { + NonNull::new(match args { + Some(args) => { + let args = args + .iter() + .map(|v| v.0.inner_val) + .collect::>(); + let ptr = args.as_ptr(); + unsafe { v8_FunctionCall(ctx.inner_ctx_ref, self.0.inner_val, args.len(), ptr) } + } + None => unsafe { + v8_FunctionCall(ctx.inner_ctx_ref, self.0.inner_val, 0, std::ptr::null()) + }, + }) + .map(|ptr| { + Self(ScopedValue { + inner_val: ptr.as_ptr(), + isolate_scope: self.0.isolate_scope, + }) + }) + } +} + +impl<'isolate_scope, 'isolate> Drop for LocalValueAny<'isolate_scope, 'isolate> { + fn drop(&mut self) { + if !self.0.inner_val.is_null() { + unsafe { v8_FreeValue(self.0.inner_val) } + } + } +} + +impl<'isolate_scope, 'isolate> TryFrom> for String { + type Error = &'static str; + + fn try_from(val: LocalValueAny<'isolate_scope, 'isolate>) -> Result { + LocalUtf8::try_from(val).map(|ls| ls.as_str().to_owned()) + } +} + +impl<'isolate_scope, 'isolate> TryFrom> for bool { + type Error = &'static str; + + fn try_from(val: LocalValueAny<'isolate_scope, 'isolate>) -> Result { + if !val.is_boolean() { + return Err("Value is not a boolean"); + } + + Ok(unsafe { val.get_boolean() }) + } +} diff --git a/src/v8/types/array.rs b/src/v8/types/array.rs new file mode 100644 index 0000000..6374ff5 --- /dev/null +++ b/src/v8/types/array.rs @@ -0,0 +1,148 @@ +/* + * Copyright Redis Ltd. 2022 - present + * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or + * the Server Side Public License v1 (SSPLv1). + */ +//! The Javascript array facilities, represented by the type +//! [LocalArray]. + +use crate::v8_c_raw::bindings::{ + v8_ArrayGet, v8_ArrayLen, v8_ArrayToValue, v8_FreeArray, v8_NewArray, v8_local_array, +}; + +use crate::v8::context_scope::ContextScope; +use crate::v8::isolate_scope::IsolateScope; +use crate::v8::types::ScopedValue; + +use super::any::LocalValueAny; +use super::Value; + +/// JS array. +#[derive(Debug, Clone)] +pub struct LocalArray<'isolate_scope, 'isolate>( + pub(crate) ScopedValue<'isolate_scope, 'isolate, v8_local_array>, +); + +impl<'isolate_scope, 'isolate> LocalArray<'isolate_scope, 'isolate> { + /// Creates a new array within the provided [IsolateScope]. + pub fn new( + values: &[&LocalValueAny], + isolate_scope: &'isolate_scope IsolateScope<'isolate>, + ) -> Self { + let args = values + .iter() + .map(|v| v.0.inner_val) + .collect::>(); + let ptr = args.as_ptr(); + let inner_val = + unsafe { v8_NewArray(isolate_scope.isolate.inner_isolate, ptr, values.len()) }; + Self(ScopedValue { + inner_val, + isolate_scope, + }) + } + + /// Returns the length of the array. + pub fn len(&self) -> usize { + unsafe { v8_ArrayLen(self.0.inner_val) } + } + + /// Returns true if the array is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Returns an iterator to the array's objects. + pub fn iter<'array, 'context_scope>( + &'array self, + context_scope: &'context_scope ContextScope<'isolate_scope, 'isolate>, + ) -> V8LocalArrayIterator<'context_scope, 'array, 'isolate_scope, 'isolate> { + V8LocalArrayIterator { + index: 0, + array: self, + context: context_scope, + } + } + + /// Returns a single object stored within the array. + pub fn get( + &self, + ctx_scope: &ContextScope, + index: usize, + ) -> LocalValueAny<'isolate_scope, 'isolate> { + let inner_val = unsafe { v8_ArrayGet(ctx_scope.inner_ctx_ref, self.0.inner_val, index) }; + LocalValueAny(ScopedValue { + inner_val, + isolate_scope: self.0.isolate_scope, + }) + } +} + +impl<'isolate_scope, 'isolate> From> + for LocalValueAny<'isolate_scope, 'isolate> +{ + fn from(array: LocalArray<'isolate_scope, 'isolate>) -> Self { + let inner_val = unsafe { v8_ArrayToValue(array.0.inner_val) }; + LocalValueAny(ScopedValue { + inner_val, + isolate_scope: array.0.isolate_scope, + }) + } +} + +/// An iterator over the objects stored within the [`V8LocalArray`]. +pub struct V8LocalArrayIterator<'context_scope, 'array, 'isolate_scope, 'isolate> { + index: usize, + array: &'array LocalArray<'isolate_scope, 'isolate>, + context: &'context_scope ContextScope<'isolate_scope, 'isolate>, +} + +impl<'context_scope, 'array, 'isolate_scope, 'isolate> Iterator + for V8LocalArrayIterator<'context_scope, 'array, 'isolate_scope, 'isolate> +{ + type Item = LocalValueAny<'isolate_scope, 'isolate>; + + fn next(&mut self) -> Option { + if self.index >= self.array.len() { + return None; + } + + let value = self.array.get(self.context, self.index); + self.index += 1; + Some(value) + } +} + +impl<'isolate_scope, 'isolate> Drop for LocalArray<'isolate_scope, 'isolate> { + fn drop(&mut self) { + unsafe { v8_FreeArray(self.0.inner_val) } + } +} + +impl<'isolate_scope, 'isolate> TryFrom> + for LocalArray<'isolate_scope, 'isolate> +{ + type Error = &'static str; + + fn try_from(val: LocalValueAny<'isolate_scope, 'isolate>) -> Result { + if !val.is_array() { + return Err("Value is not an array"); + } + + Ok(unsafe { val.as_array() }) + } +} + +impl<'isolate_scope, 'isolate> TryFrom> + for LocalArray<'isolate_scope, 'isolate> +{ + type Error = &'static str; + + fn try_from(val: Value<'isolate_scope, 'isolate>) -> Result { + match val { + Value::Array(array) => Ok(array), + Value::Other(any) => any.try_into(), + _ => Err("Value is not an array"), + } + } +} diff --git a/src/v8/types/array_buffer.rs b/src/v8/types/array_buffer.rs new file mode 100644 index 0000000..f4775af --- /dev/null +++ b/src/v8/types/array_buffer.rs @@ -0,0 +1,95 @@ +/* + * Copyright Redis Ltd. 2022 - present + * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or + * the Server Side Public License v1 (SSPLv1). + */ +//! The Javascript array buffer facilities, represented by the type +//! [LocalArrayBuffer]. The types [super::LocalArray] and +//! [LocalArrayBuffer] are different, the first stores valid javascript +//! objects, the latter stores bytes. + +use crate::v8_c_raw::bindings::{ + v8_ArrayBufferGetData, v8_ArrayBufferToValue, v8_FreeArrayBuffer, v8_NewArrayBuffer, + v8_local_array_buff, +}; + +use crate::v8::isolate_scope::IsolateScope; +use crate::v8::types::ScopedValue; + +use super::any::LocalValueAny; +use super::Value; + +/// JavaScript array buffer. +#[derive(Debug, Clone)] +pub struct LocalArrayBuffer<'isolate_scope, 'isolate>( + pub(crate) ScopedValue<'isolate_scope, 'isolate, v8_local_array_buff>, +); + +impl<'isolate_scope, 'isolate> LocalArrayBuffer<'isolate_scope, 'isolate> { + /// Creates a new local array buffer within the provided [IsolateScope]. + pub fn new(bytes: &[u8], isolate_scope: &'isolate_scope IsolateScope<'isolate>) -> Self { + let inner_val = unsafe { + v8_NewArrayBuffer( + isolate_scope.isolate.inner_isolate, + bytes.as_ptr() as *const _, + bytes.len(), + ) + }; + + Self(ScopedValue { + inner_val, + isolate_scope, + }) + } + + /// Returns a byte slice to the stored data. + pub fn data(&self) -> &[u8] { + let mut size = 0; + let data = unsafe { v8_ArrayBufferGetData(self.0.inner_val, &mut size as *mut usize) }; + unsafe { std::slice::from_raw_parts(data.cast::(), size) } + } +} + +impl<'isolate_scope, 'isolate> Drop for LocalArrayBuffer<'isolate_scope, 'isolate> { + fn drop(&mut self) { + unsafe { v8_FreeArrayBuffer(self.0.inner_val) } + } +} + +impl<'isolate_scope, 'isolate> From> + for LocalValueAny<'isolate_scope, 'isolate> +{ + fn from(value: LocalArrayBuffer<'isolate_scope, 'isolate>) -> Self { + let inner_val = unsafe { v8_ArrayBufferToValue(value.0.inner_val) }; + LocalValueAny(ScopedValue { + inner_val, + isolate_scope: value.0.isolate_scope, + }) + } +} + +impl<'isolate_scope, 'isolate> TryFrom> + for LocalArrayBuffer<'isolate_scope, 'isolate> +{ + type Error = &'static str; + fn try_from(val: LocalValueAny<'isolate_scope, 'isolate>) -> Result { + if !val.is_array_buffer() { + return Err("Value is not an array buffer"); + } + + Ok(unsafe { val.as_array_buffer() }) + } +} + +impl<'isolate_scope, 'isolate> TryFrom> + for LocalArrayBuffer<'isolate_scope, 'isolate> +{ + type Error = &'static str; + fn try_from(val: Value<'isolate_scope, 'isolate>) -> Result { + match val { + Value::ArrayBuffer(array_buffer) => Ok(array_buffer), + Value::Other(any) => any.try_into(), + _ => Err("Value is not an array buffer"), + } + } +} diff --git a/src/v8/types/external_data.rs b/src/v8/types/external_data.rs new file mode 100644 index 0000000..e6d3b85 --- /dev/null +++ b/src/v8/types/external_data.rs @@ -0,0 +1,90 @@ +/* + * Copyright Redis Ltd. 2022 - present + * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or + * the Server Side Public License v1 (SSPLv1). + */ +//! Provides a way to allow the JavaScript code to interoperate with +//! foreign objects. + +// TODO make the objects of this structure store a tag of the type, so +// that for get_data/get_data_mut it is checked before the conversion. +// +// This should help when an object of type `X` is created and stored, +// but the extraction is performed for another type `Y`, what may bring +// the undefined behaviour. + +use crate::v8_c_raw::bindings::{ + v8_ExternalDataGet, v8_ExternalDataToValue, v8_NewExternalData, v8_local_external_data, +}; + +use crate::v8::isolate_scope::IsolateScope; +use crate::v8::types::ScopedValue; + +use super::any::LocalValueAny; + +extern "C" fn free_external_data(arg1: *mut ::std::os::raw::c_void) { + unsafe { Box::from_raw(arg1 as *mut T) }; +} + +/// The V8's `External` type. +/// This is a JavaScript value that wraps a C++ void*. +/// This type is mainly used to associate non-javascript-native data +/// structures with JavaScript objects. +#[derive(Debug, Clone)] +pub struct LocalExternalData<'isolate_scope, 'isolate>( + pub(crate) ScopedValue<'isolate_scope, 'isolate, v8_local_external_data>, +); + +impl<'isolate_scope, 'isolate> LocalExternalData<'isolate_scope, 'isolate> { + /// Creates a new local external data object within the passed [IsolateScope]. + pub fn new(data: T, isolate_scope: &'isolate_scope IsolateScope<'isolate>) -> Self { + let data = Box::into_raw(Box::new(data)); + let inner_val = unsafe { + v8_NewExternalData( + isolate_scope.isolate.inner_isolate, + data as *mut _, + Some(free_external_data::), + ) + }; + Self(ScopedValue { + inner_val, + isolate_scope, + }) + } + + /// Returns the data stored, as an immutable reference to `T`. + pub fn get_data(&self) -> &'isolate_scope T { + unsafe { &*(v8_ExternalDataGet(self.0.inner_val) as *const T) } + } + + /// Returns the data stored, as a mutable reference to `T`. + pub fn get_data_mut(&mut self) -> &mut T { + unsafe { &mut *(v8_ExternalDataGet(self.0.inner_val) as *mut T) } + } +} + +impl<'isolate_scope, 'isolate> From> + for LocalValueAny<'isolate_scope, 'isolate> +{ + fn from(value: LocalExternalData<'isolate_scope, 'isolate>) -> Self { + let inner_val = unsafe { v8_ExternalDataToValue(value.0.inner_val) }; + LocalValueAny(ScopedValue { + inner_val, + isolate_scope: value.0.isolate_scope, + }) + } +} + +impl<'isolate_scope, 'isolate> TryFrom> + for LocalExternalData<'isolate_scope, 'isolate> +{ + type Error = &'static str; + + fn try_from(val: LocalValueAny<'isolate_scope, 'isolate>) -> Result { + if !val.is_external_data() { + return Err("Value is not a string"); + } + + Ok(unsafe { val.as_external_data() }) + } +} diff --git a/src/v8/types/mod.rs b/src/v8/types/mod.rs new file mode 100644 index 0000000..0f26a65 --- /dev/null +++ b/src/v8/types/mod.rs @@ -0,0 +1,733 @@ +/* + * Copyright Redis Ltd. 2022 - present + * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or + * the Server Side Public License v1 (SSPLv1). + */ +//! This module contains the supported JavaScript V8 value types. +//! See [Value], [LocalValueAny] types for the generic implementations. +//! +//! The objects cannot be created without having an isolate scope within +//! which they should be created. All the objects' lifetimes are limited +//! to the isolate scope lifetime they were created for. +//! +//! All the concrete types are a subtype of a more generic base type +//! [ScopedValue] which contains the isolate scope and a generic type +//! argument for the value. +//! +//! Sometimes, the values are convertible to and from each other, either +//! via the [From] or [TryFrom] traits, depending on the possibility of +//! such a conversion. +//! +//! The most commonly used type is the V8's type-erased +//! [v8_c_raw::bindings::v8_local_value] mutable pointer, which is +//! represented by [LocalValueAny]. + +use crate::v8_c_raw::bindings::{v8_NewBool, v8_NewNull, v8_ValueFromDouble, v8_ValueFromLong}; + +pub mod any; +pub mod array; +pub mod array_buffer; +pub mod external_data; +pub mod module; +pub mod native_function; +pub mod native_function_template; +pub mod object; +pub mod object_template; +pub mod persistent; +pub mod promise; +pub mod resolver; +pub mod script; +pub mod set; +pub mod string; +pub mod try_catch; +pub mod unlocker; +pub mod utf8; + +use crate::v8::context_scope::ContextScope; +use crate::v8::isolate_scope::IsolateScope; +use crate::v8::OptionalTryFrom; +use array::LocalArray; +use array_buffer::LocalArrayBuffer; +use external_data::LocalExternalData; +use native_function_template::LocalNativeFunctionArgsIter; +use object::LocalObject; +use promise::LocalPromise; +use set::LocalSet; +use string::LocalString; +use utf8::LocalUtf8; + +use self::any::{LocalValueAny, Type}; +use self::native_function_template::{LocalNativeFunctionArgs, LocalNativeFunctionTemplate}; +use self::object_template::LocalObjectTemplate; +use self::try_catch::TryCatch; +use self::unlocker::Unlocker; + +/// A generic, isolate-scoped JavaScript value. +#[derive(Debug, Copy, Clone)] +pub struct ScopedValue<'isolate_scope, 'isolate, BindingType: std::fmt::Debug> { + pub(crate) inner_val: *mut BindingType, + pub(crate) isolate_scope: &'isolate_scope IsolateScope<'isolate>, +} + +/// An isolate-scoped `Number` local value in JavaScript. +/// Can be converted into [f64]. +#[repr(transparent)] +#[derive(Debug, Clone)] +pub struct LocalValueNumber<'isolate_scope, 'isolate>( + pub(crate) LocalValueAny<'isolate_scope, 'isolate>, +); + +impl<'isolate_scope, 'isolate> LocalValueNumber<'isolate_scope, 'isolate> { + fn new(value: f64, isolate_scope: &'isolate_scope IsolateScope<'isolate>) -> Self { + let inner_val = unsafe { v8_ValueFromDouble(isolate_scope.isolate.inner_isolate, value) }; + Self(LocalValueAny(ScopedValue { + inner_val, + isolate_scope, + })) + } +} + +impl<'isolate_scope, 'isolate> From> for f64 { + fn from(value: LocalValueNumber<'isolate_scope, 'isolate>) -> Self { + unsafe { value.0.get_number() } + } +} + +impl<'isolate_scope, 'isolate> TryFrom> for f64 { + type Error = &'static str; + + fn try_from(val: LocalValueAny<'isolate_scope, 'isolate>) -> Result { + Ok(LocalValueNumber::try_from(val)?.into()) + } +} + +impl<'isolate_scope, 'isolate> TryFrom> + for LocalValueNumber<'isolate_scope, 'isolate> +{ + type Error = &'static str; + + fn try_from(val: LocalValueAny<'isolate_scope, 'isolate>) -> Result { + if !val.is_number() { + return Err("Value is not a number"); + } + + Ok(Self(val)) + } +} + +impl<'isolate_scope, 'isolate> TryFrom> + for LocalValueNumber<'isolate_scope, 'isolate> +{ + type Error = &'static str; + + fn try_from(val: Value<'isolate_scope, 'isolate>) -> Result { + match val { + Value::Number(n) => Ok(n), + Value::Other(any) => any.try_into(), + _ => Err("Value is not a number"), + } + } +} + +/// An isolate-scoped `BigInt` local value in JavaScript. +/// Can be converted into [i64]. +#[repr(transparent)] +#[derive(Debug, Clone)] +pub struct LocalValueBigInteger<'isolate_scope, 'isolate>( + pub(crate) LocalValueAny<'isolate_scope, 'isolate>, +); + +impl<'isolate_scope, 'isolate> LocalValueBigInteger<'isolate_scope, 'isolate> { + fn new(value: i64, isolate_scope: &'isolate_scope IsolateScope<'isolate>) -> Self { + let inner_val = unsafe { v8_ValueFromLong(isolate_scope.isolate.inner_isolate, value) }; + Self(LocalValueAny(ScopedValue { + inner_val, + isolate_scope, + })) + } +} + +impl<'isolate_scope, 'isolate> From> for i64 { + fn from(value: LocalValueBigInteger<'isolate_scope, 'isolate>) -> Self { + unsafe { value.0.get_long() } + } +} + +impl<'isolate_scope, 'isolate> TryFrom> + for LocalValueBigInteger<'isolate_scope, 'isolate> +{ + type Error = &'static str; + + fn try_from(val: LocalValueAny<'isolate_scope, 'isolate>) -> Result { + if !val.is_long() { + return Err("Value is not a long"); + } + + Ok(Self(val)) + } +} + +/// An isolate-scoped [bool] local value in JavaScript. +#[repr(transparent)] +#[derive(Debug, Clone)] +pub struct LocalValueBoolean<'isolate_scope, 'isolate>( + pub(crate) LocalValueAny<'isolate_scope, 'isolate>, +); + +impl<'isolate_scope, 'isolate> LocalValueBoolean<'isolate_scope, 'isolate> { + fn new(value: bool, isolate_scope: &'isolate_scope IsolateScope<'isolate>) -> Self { + let inner_val = unsafe { v8_NewBool(isolate_scope.isolate.inner_isolate, value as i32) }; + Self(LocalValueAny(ScopedValue { + inner_val, + isolate_scope, + })) + } +} + +impl<'isolate_scope, 'isolate> From> for bool { + fn from(value: LocalValueBoolean<'isolate_scope, 'isolate>) -> Self { + unsafe { value.0.get_boolean() } + } +} + +impl<'isolate_scope, 'isolate> TryFrom> + for LocalValueBoolean<'isolate_scope, 'isolate> +{ + type Error = &'static str; + + fn try_from(val: LocalValueAny<'isolate_scope, 'isolate>) -> Result { + if !val.is_boolean() { + return Err("Value is not a boolean"); + } + + Ok(Self(val)) + } +} + +/// An isolate-scoped `null` local value in JavaScript. +#[repr(transparent)] +#[derive(Debug, Clone)] +pub struct LocalValueNull<'isolate_scope, 'isolate>( + pub(crate) LocalValueAny<'isolate_scope, 'isolate>, +); + +impl<'isolate_scope, 'isolate> LocalValueNull<'isolate_scope, 'isolate> { + fn new(isolate_scope: &'isolate_scope IsolateScope<'isolate>) -> Self { + let inner_val = unsafe { v8_NewNull(isolate_scope.isolate.inner_isolate) }; + Self(LocalValueAny(ScopedValue { + inner_val, + isolate_scope, + })) + } +} + +impl<'isolate_scope, 'isolate> From<&'isolate_scope IsolateScope<'isolate>> + for LocalValueNull<'isolate_scope, 'isolate> +{ + fn from(value: &'isolate_scope IsolateScope<'isolate>) -> Self { + Self::new(value) + } +} + +impl<'isolate_scope, 'isolate> TryFrom> + for LocalValueNull<'isolate_scope, 'isolate> +{ + type Error = &'static str; + + fn try_from(val: LocalValueAny<'isolate_scope, 'isolate>) -> Result { + if !val.is_null() { + return Err("Value is not a null"); + } + + Ok(Self(val)) + } +} + +/// All the possible values a JavaScript may have with which it is +/// possible to work. +#[derive(Debug, Clone)] +pub enum Value<'isolate_scope, 'isolate> { + /// See [LocalString]. + String(LocalString<'isolate_scope, 'isolate>), + /// See [LocalValueBoolean]. + Boolean(LocalValueBoolean<'isolate_scope, 'isolate>), + /// See [LocalValueBigInteger]. + BigInteger(LocalValueBigInteger<'isolate_scope, 'isolate>), + /// See [LocalValueNumber]. + Number(LocalValueNumber<'isolate_scope, 'isolate>), + /// See [LocalArrayBuffer]. + ArrayBuffer(LocalArrayBuffer<'isolate_scope, 'isolate>), + /// See [LocalArray]. + Array(LocalArray<'isolate_scope, 'isolate>), + /// See [LocalSet]. + Set(LocalSet<'isolate_scope, 'isolate>), + /// See [LocalObject]. + Object(LocalObject<'isolate_scope, 'isolate>), + /// See [LocalObjectTemplate]. + ObjectTemplate(LocalObjectTemplate<'isolate_scope, 'isolate>), + /// See [TryCatch]. + TryCatch(TryCatch<'isolate_scope, 'isolate>), + /// See [Unlocker]. + Unlocker(Unlocker<'isolate_scope, 'isolate>), + /// See [LocalNativeFunctionTemplate]. + NativeFunctionTemplate(LocalNativeFunctionTemplate<'isolate_scope, 'isolate>), + /// See [LocalValueNull]. + Null(LocalValueNull<'isolate_scope, 'isolate>), + /// See [LocalExternalData]. + ExternalData(LocalExternalData<'isolate_scope, 'isolate>), + /// See [LocalValueAny]. + Other(LocalValueAny<'isolate_scope, 'isolate>), +} + +impl<'isolate_scope, 'isolate> Value<'isolate_scope, 'isolate> { + /// Creates a new double local value from [f64] for the passed [IsolateScope]. + pub fn from_f64(value: f64, isolate_scope: &'isolate_scope IsolateScope<'isolate>) -> Self { + Value::Number(LocalValueNumber::new(value, isolate_scope)) + } + + /// Creates a new integer local value from [i64] for the passed [IsolateScope]. + pub fn from_i64(value: i64, isolate_scope: &'isolate_scope IsolateScope<'isolate>) -> Self { + Value::BigInteger(LocalValueBigInteger::new(value, isolate_scope)) + } + + /// Creates a new boolean local value from [bool] for the passed [IsolateScope]. + pub fn from_bool(value: bool, isolate_scope: &'isolate_scope IsolateScope<'isolate>) -> Self { + Value::Boolean(LocalValueBoolean::new(value, isolate_scope)) + } + + /// Creates a new local object for the passed [IsolateScope]. + pub fn new_object(isolate_scope: &'isolate_scope IsolateScope<'isolate>) -> Self { + Value::Object(LocalObject::new(isolate_scope)) + } + + /// Creates a new local object template for the passed [IsolateScope]. + pub fn new_object_template(isolate_scope: &'isolate_scope IsolateScope<'isolate>) -> Self { + Value::ObjectTemplate(LocalObjectTemplate::new(isolate_scope)) + } + + /// Creates a new null local object for the passed [IsolateScope]. + pub fn new_null(isolate_scope: &'isolate_scope IsolateScope<'isolate>) -> Self { + Value::Null(LocalValueNull::new(isolate_scope)) + } + + /// Creates a new local set for the passed [IsolateScope]. + pub fn new_set(isolate_scope: &'isolate_scope IsolateScope<'isolate>) -> Self { + Value::Set(LocalSet::new(isolate_scope)) + } + + /// Creates a new local string for the passed [IsolateScope]. + pub fn from_str(s: &str, isolate_scope: &'isolate_scope IsolateScope<'isolate>) -> Self { + Value::String(LocalString::new(s, isolate_scope)) + } + + /// Creates a new local try catch for the passed [IsolateScope]. + pub fn new_try_catch(isolate_scope: &'isolate_scope IsolateScope<'isolate>) -> Self { + Value::TryCatch(TryCatch::new(isolate_scope)) + } + + /// Creates a new local array buffer for the passed [IsolateScope]. + pub fn from_array_buffer( + bytes: &[u8], + isolate_scope: &'isolate_scope IsolateScope<'isolate>, + ) -> Self { + Value::ArrayBuffer(LocalArrayBuffer::new(bytes, isolate_scope)) + } + + /// Creates a new local array for the passed [IsolateScope]. + pub fn from_array( + values: &[&LocalValueAny], + isolate_scope: &'isolate_scope IsolateScope<'isolate>, + ) -> Self { + Value::Array(LocalArray::new(values, isolate_scope)) + } + + /// Creates a new unlocker for the passed [IsolateScope]. + pub fn new_unlocker(isolate_scope: &'isolate_scope IsolateScope<'isolate>) -> Self { + Value::Unlocker(Unlocker::new(isolate_scope)) + } + + /// Creates a new external data object for the passed [IsolateScope]. + pub fn from_external_data( + data: T, + isolate_scope: &'isolate_scope IsolateScope<'isolate>, + ) -> Self { + Value::ExternalData(LocalExternalData::new(data, isolate_scope)) + } + + /// Creates a new native function object for the passed [IsolateScope]. + pub fn new_native_function_template< + T: for<'d, 'c> Fn( + &LocalNativeFunctionArgs<'d, 'c>, + &'d IsolateScope<'c>, + &ContextScope<'d, 'c>, + ) -> Option>, + >( + function: T, + isolate_scope: &'isolate_scope IsolateScope<'isolate>, + ) -> Self { + Value::NativeFunctionTemplate(LocalNativeFunctionTemplate::new(function, isolate_scope)) + } + + /// Returns `true` if the value stored is a JavaScript object. + pub fn is_object(&self) -> bool { + LocalObject::try_from(self.clone()).is_ok() + } + + /// Returns `true` if the value stored is a JavaScript string. + pub fn is_string(&self) -> bool { + LocalString::try_from(self.clone()).is_ok() + } + + /// Returns `true` if the value stored is a JavaScript `Number`. + pub fn is_number(&self) -> bool { + LocalValueNumber::try_from(self.clone()).is_ok() + } + + /// Returns `true` if the value stored is a JavaScript `Promise`. + pub fn is_promise(&self) -> bool { + LocalPromise::try_from(self.clone()).is_ok() + } + + /// Returns `true` if the value stored is a JavaScript `Array`. + pub fn is_array(&self) -> bool { + LocalArray::try_from(self.clone()).is_ok() + } +} + +impl<'isolate_scope, 'isolate> From> + for Value<'isolate_scope, 'isolate> +{ + fn from(value: LocalValueAny<'isolate_scope, 'isolate>) -> Self { + // TODO rewrite better than this. + match value.get_type() { + Some(Type::Array) => { + Self::Array(LocalArray::try_from(value).expect("Conversion error")) + } + Some(Type::ArrayBuffer) => { + Self::ArrayBuffer(LocalArrayBuffer::try_from(value).expect("Conversion error")) + } + Some(Type::String) => { + Self::String(LocalString::try_from(value).expect("Conversion error")) + } + Some(Type::BigInteger) => { + Self::BigInteger(LocalValueBigInteger::try_from(value).expect("Conversion error")) + } + Some(Type::Number) => { + Self::Number(LocalValueNumber::try_from(value).expect("Conversion error")) + } + Some(Type::Set) => Self::Set(LocalSet::try_from(value).expect("Conversion error")), + Some(Type::Object) => { + Self::Object(LocalObject::try_from(value).expect("Conversion error")) + } + Some(Type::Null) => { + Self::Null(LocalValueNull::try_from(value).expect("Conversion error")) + } + Some(Type::ExternalData) => { + Self::ExternalData(LocalExternalData::try_from(value).expect("Conversion error")) + } + _ => Self::Other(value), + } + } +} + +impl<'isolate_scope, 'isolate> TryFrom> + for LocalString<'isolate_scope, 'isolate> +{ + type Error = &'static str; + + fn try_from(val: Value<'isolate_scope, 'isolate>) -> Result { + match val { + Value::String(ls) => Ok(ls), + Value::Other(any) => any.try_into(), + _ => Err("Value is not a string."), + } + } +} + +impl<'isolate_scope, 'isolate> TryFrom> + for LocalUtf8<'isolate_scope, 'isolate> +{ + type Error = &'static str; + + fn try_from(val: Value<'isolate_scope, 'isolate>) -> Result { + match val { + Value::String(s) => LocalValueAny::from(s).try_into(), + Value::Other(any) => any.try_into(), + _ => Err("Value is not string"), + } + } +} + +impl<'isolate_scope, 'isolate> TryFrom> + for LocalPromise<'isolate_scope, 'isolate> +{ + type Error = &'static str; + + fn try_from(val: Value<'isolate_scope, 'isolate>) -> Result { + if let Value::Other(any) = val { + if any.is_promise() { + return Ok(unsafe { any.as_promise() }); + } + } + Err("Value is not a promise") + } +} + +impl<'isolate_scope, 'isolate> TryFrom> + for LocalObjectTemplate<'isolate_scope, 'isolate> +{ + type Error = &'static str; + + fn try_from(val: Value<'isolate_scope, 'isolate>) -> Result { + if let Value::ObjectTemplate(object_template) = val { + Ok(object_template) + } else { + Err("Value is not a promise") + } + } +} + +impl<'isolate_scope, 'isolate> TryFrom> + for TryCatch<'isolate_scope, 'isolate> +{ + type Error = &'static str; + + fn try_from(val: Value<'isolate_scope, 'isolate>) -> Result { + if let Value::TryCatch(tc) = val { + Ok(tc) + } else { + Err("Value is not a try catch") + } + } +} + +impl<'isolate_scope, 'isolate> TryFrom> + for LocalNativeFunctionTemplate<'isolate_scope, 'isolate> +{ + type Error = &'static str; + + fn try_from(val: Value<'isolate_scope, 'isolate>) -> Result { + if let Value::NativeFunctionTemplate(t) = val { + Ok(t) + } else { + Err("Value is not a local function template") + } + } +} + +impl<'isolate_scope, 'isolate> TryFrom> for bool { + type Error = &'static str; + + fn try_from(val: Value<'isolate_scope, 'isolate>) -> Result { + Ok(match val { + Value::Boolean(b) => b.into(), + Value::Other(any) => { + if !any.is_boolean() { + return Err("Value is not a boolean."); + } + + unsafe { any.get_boolean() } + } + _ => return Err("Value is not a long."), + }) + } +} + +impl<'isolate_scope, 'isolate> TryFrom> for i64 { + type Error = &'static str; + + fn try_from(val: Value<'isolate_scope, 'isolate>) -> Result { + Ok(match val { + Value::BigInteger(i) => i.into(), + Value::Other(any) => { + if !any.is_long() { + return Err("Value is not a long."); + } + + unsafe { any.get_long() } + } + _ => return Err("Value is not a long."), + }) + } +} + +impl<'isolate_scope, 'isolate> TryFrom> for f64 { + type Error = &'static str; + + fn try_from(val: Value<'isolate_scope, 'isolate>) -> Result { + Ok(match val { + Value::Number(f) => f.into(), + Value::Other(any) => { + if !any.is_number() { + return Err("Value is not a number"); + } + + unsafe { any.get_number() } + } + _ => return Err("Value is not a number."), + }) + } +} + +impl<'isolate_scope, 'isolate> TryFrom> for String { + type Error = &'static str; + + fn try_from(val: Value<'isolate_scope, 'isolate>) -> Result { + match val { + Value::String(ls) => Ok(LocalUtf8::from(ls).into()), + // Value::Object(o) => String::try_from(o), + Value::Other(any) => String::try_from(any), + _ => Err("Value is not a string."), + } + } +} + +impl<'isolate_scope, 'isolate> TryFrom> + for LocalValueAny<'isolate_scope, 'isolate> +{ + type Error = &'static str; + + fn try_from(val: Value<'isolate_scope, 'isolate>) -> Result { + match val { + Value::Array(array) => Ok(array.into()), + Value::ArrayBuffer(array_buffer) => Ok(array_buffer.into()), + Value::Boolean(boolean) => Ok(boolean.0), + Value::BigInteger(integer) => Ok(integer.0), + Value::Number(double) => Ok(double.0), + Value::Null(null) => Ok(null.0), + Value::String(string) => Ok(string.into()), + Value::Set(set) => Ok(set.into()), + Value::Object(object) => Ok(object.into()), + // LocalValue::ObjectTemplate(object_template) => Ok(object_template.into()), + // LocalValue::TryCatch(try_catch) => Ok(try_catch.into()), + // LocalValue::Unlocker(unlocker) => Ok(unlocker.into()), + // LocalValue::NativeFunctionTemplate(native_function_template) => { + // Ok(native_function_template.into()) + // } + Value::ExternalData(external_data) => Ok(external_data.into()), + Value::Other(any) => Ok(any), + _ => Err("Couldn't convert to any"), + } + } +} + +macro_rules! from_iter_impl { + ( $x:ty ) => { + impl<'isolate_scope, 'isolate, 'a> + TryFrom<&mut LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a>> for $x + { + type Error = &'static str; + + fn try_from( + val: &mut LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a>, + ) -> Result { + match val.next() { + Some(val) => std::convert::TryInto::<$x>::try_into(val), + None => Err("Wrong number of arguments given".into()), + } + } + } + }; +} + +from_iter_impl!(i64); +from_iter_impl!(f64); +from_iter_impl!(String); +from_iter_impl!(bool); +from_iter_impl!(LocalArray<'isolate_scope, 'isolate>); +from_iter_impl!(LocalArrayBuffer<'isolate_scope, 'isolate>); +from_iter_impl!(LocalObject<'isolate_scope, 'isolate>); +from_iter_impl!(LocalSet<'isolate_scope, 'isolate>); +from_iter_impl!(LocalUtf8<'isolate_scope, 'isolate>); + +impl<'isolate_scope, 'isolate, 'a> + TryFrom<&mut LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a>> + for Value<'isolate_scope, 'isolate> +{ + type Error = &'static str; + + fn try_from( + val: &mut LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a>, + ) -> Result { + val.next().ok_or("Wrong number of arguments given") + } +} + +impl<'isolate_scope, 'isolate, 'a, T> + OptionalTryFrom<&mut LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a>> for T +where + T: TryFrom, Error = &'static str>, +{ + type Error = &'static str; + + fn optional_try_from( + val: &mut LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a>, + ) -> Result, Self::Error> { + let val = match val.next() { + Some(v) => v, + None => return Ok(None), + }; + let val = LocalValueAny::try_from(val)?; + let val = val.try_into()?; + Ok(Some(val)) + } +} + +impl<'isolate_scope, 'isolate, 'a> + OptionalTryFrom<&mut LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a>> + for Value<'isolate_scope, 'isolate> +{ + type Error = &'static str; + + fn optional_try_from( + val: &mut LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a>, + ) -> Result, Self::Error> { + Ok(val.next()) + } +} + +// impl<'isolate_scope, 'isolate, 'a, T> +// TryFrom<&mut V8LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a>> for Vec +// where +// T: TryFrom, Error = &'static str>, +// { +// type Error = &'static str; + +// fn try_from( +// val: &mut V8LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a>, +// ) -> Result { +// let mut res = Self::new(); +// for v in val { +// let v = LocalValueAny::try_from(v)?.try_into()?; +// res.push(v); +// } +// Ok(res) +// } +// } + +impl<'isolate_scope, 'isolate, 'a> + TryFrom<&mut LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a>> + for Vec> +{ + type Error = &'static str; + + fn try_from( + val: &mut LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a>, + ) -> Result { + Ok(val.collect()) + } +} + +impl<'isolate_scope, 'isolate, 'a, T> + TryFrom<&mut LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a>> for Vec +where + T: TryFrom, Error = &'static str>, +{ + type Error = &'static str; + + fn try_from( + val: &mut LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a>, + ) -> Result { + Ok(val.map(|v| T::try_from(v).unwrap()).collect()) + } +} diff --git a/src/v8/types/module.rs b/src/v8/types/module.rs new file mode 100644 index 0000000..35bfcda --- /dev/null +++ b/src/v8/types/module.rs @@ -0,0 +1,192 @@ +/* + * Copyright Redis Ltd. 2022 - present + * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or + * the Server Side Public License v1 (SSPLv1). + */ +//! A collection of types of data which represent module objects. + +use crate::v8_c_raw::bindings::{ + v8_ContextRefGetIsolate, v8_EvaluateModule, v8_FreeModule, v8_FreePersistedModule, + v8_InitiateModule, v8_ModuleGetIdentityHash, v8_ModulePersist, v8_ModuleToLocal, + v8_context_ref, v8_local_module, v8_local_string, v8_persisted_module, +}; +use crate::RawIndex; + +use crate::v8::context_scope::ContextScope; +use crate::v8::isolate::Isolate; +use crate::v8::isolate_scope::IsolateScope; +use crate::v8::types::LocalString; +use crate::v8::types::ScopedValue; +use std::os::raw::c_int; +use std::ptr; + +use super::any::LocalValueAny; +use super::Value; + +/// A module in JavaScript is a collection of functions and objects. +/// An unitialised module cannot be evaluated and must be initialised +/// prior to any use. +pub struct UninitialisedLocalModule<'isolate_scope, 'isolate>( + pub(crate) ScopedValue<'isolate_scope, 'isolate, v8_local_module>, +); + +/// An initialised module. +pub struct InitialisedLocalModule<'isolate_scope, 'isolate>( + pub(crate) ScopedValue<'isolate_scope, 'isolate, v8_local_module>, +); + +/// The same as [LocalModule] but not tied to an [IsolateScope]. +pub struct PersistedModule { + pub(crate) inner_persisted_module: *mut v8_persisted_module, +} + +pub(crate) extern "C" fn load_module< + T: for<'isolate, 'isolate_scope, 'c> Fn( + &'isolate IsolateScope<'c>, + &'isolate ContextScope<'isolate_scope, 'c>, + &'isolate LocalString<'isolate_scope, 'c>, + i64, + ) + -> Option>, +>( + v8_ctx_ref: *mut v8_context_ref, + name: *mut v8_local_string, + identity_hash: c_int, +) -> *mut v8_local_module { + let isolate = Isolate { + inner_isolate: unsafe { v8_ContextRefGetIsolate(v8_ctx_ref) }, + no_release: true, + }; + let isolate_scope = IsolateScope::new(&isolate); + let ctx_scope = ContextScope { + inner_ctx_ref: v8_ctx_ref, + exit_on_drop: false, + isolate_scope: &isolate_scope, + }; + let name_obj = LocalString(ScopedValue { + inner_val: name, + isolate_scope: &isolate_scope, + }); + let load_callback: &T = ctx_scope.get_private_data_mut_raw(RawIndex(0)).unwrap(); + let res = load_callback(&isolate_scope, &ctx_scope, &name_obj, identity_hash as i64); + match res { + Some(mut r) => { + let inner_module = r.0.inner_val; + r.0.inner_val = ptr::null_mut(); + inner_module + } + None => ptr::null_mut(), + } +} + +impl<'isolate_scope, 'isolate> UninitialisedLocalModule<'isolate_scope, 'isolate> { + /// Initialises the module. A module must be initialised before + /// it can be evaluated. + pub fn initialize< + T: for<'c, 'd, 'e> Fn( + &'c IsolateScope<'e>, + &'c ContextScope<'d, 'e>, + &'c LocalString<'d, 'e>, + i64, + ) -> Option>, + >( + &self, + ctx_scope: &ContextScope, + load_module_callback: T, + ) -> Option> { + ctx_scope.set_private_data_raw(RawIndex(0), &load_module_callback); + let res = unsafe { + v8_InitiateModule( + self.0.inner_val, + ctx_scope.inner_ctx_ref, + Some(load_module::), + ) + }; + ctx_scope.reset_private_data_raw(RawIndex(0)); + if res != 0 { + Some(InitialisedLocalModule(ScopedValue { + inner_val: self.0.inner_val, + isolate_scope: self.0.isolate_scope, + })) + } else { + None + } + } + + /// Persists the module by allowing it outlive the [IsolateScope] it + /// has by converting it into a [PersistedModule] for the provided + /// [Isolate]. + pub fn persist(&self, isolate: &Isolate) -> PersistedModule { + let inner_persisted_module = + unsafe { v8_ModulePersist(isolate.inner_isolate, self.0.inner_val) }; + PersistedModule { + inner_persisted_module, + } + } + + /// Returns the hash value for the module. + pub fn get_identity_hash(&self) -> i64 { + unsafe { v8_ModuleGetIdentityHash(self.0.inner_val) as i64 } + } +} + +impl<'isolate_scope, 'isolate> InitialisedLocalModule<'isolate_scope, 'isolate> { + /// Evalutes the module and returns a value if it was returned by + /// the module during the evaluation. + pub fn evaluate(&self, ctx_scope: &ContextScope) -> Option> { + let res = unsafe { v8_EvaluateModule(self.0.inner_val, ctx_scope.inner_ctx_ref) }; + if res.is_null() { + None + } else { + Some( + LocalValueAny(ScopedValue { + inner_val: res, + isolate_scope: self.0.isolate_scope, + }) + .into(), + ) + } + } +} + +impl PersistedModule { + /// Returns a local module object, which is tied to the passed + /// [IsolateScope]. + pub fn as_local<'isolate_scope, 'isolate>( + &self, + isolate_scope: &'isolate_scope IsolateScope<'isolate>, + ) -> UninitialisedLocalModule<'isolate_scope, 'isolate> { + let inner_val = unsafe { + v8_ModuleToLocal( + isolate_scope.isolate.inner_isolate, + self.inner_persisted_module, + ) + }; + UninitialisedLocalModule(ScopedValue { + inner_val, + isolate_scope, + }) + } +} + +impl<'isolate_scope, 'isolate> Drop for UninitialisedLocalModule<'isolate_scope, 'isolate> { + fn drop(&mut self) { + if !self.0.inner_val.is_null() { + unsafe { v8_FreeModule(self.0.inner_val) } + } + } +} + +impl<'isolate_scope, 'isolate> Drop for InitialisedLocalModule<'isolate_scope, 'isolate> { + fn drop(&mut self) { + if !self.0.inner_val.is_null() { + unsafe { v8_FreeModule(self.0.inner_val) } + } + } +} + +impl Drop for PersistedModule { + fn drop(&mut self) { + unsafe { v8_FreePersistedModule(self.inner_persisted_module) } + } +} diff --git a/src/v8/types/native_function.rs b/src/v8/types/native_function.rs new file mode 100644 index 0000000..dc7f1cb --- /dev/null +++ b/src/v8/types/native_function.rs @@ -0,0 +1,52 @@ +/* + * Copyright Redis Ltd. 2022 - present + * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or + * the Server Side Public License v1 (SSPLv1). + */ +//! Contains the native function facilities. + +use crate::v8_c_raw::bindings::{ + v8_FreeNativeFunction, v8_NativeFunctionToValue, v8_local_native_function, +}; + +use crate::v8::types::ScopedValue; + +use super::any::LocalValueAny; +use super::Value; + +/// Native function object, which can be instantiated from a template +/// [super::LocalNativeFunctionTemplate] or created directly via +/// [crate::v8::context_scope::ContextScope::create_native_function]. +pub struct LocalNativeFunction<'isolate_scope, 'isolate>( + pub(crate) ScopedValue<'isolate_scope, 'isolate, v8_local_native_function>, +); + +impl<'isolate_scope, 'isolate> Drop for LocalNativeFunction<'isolate_scope, 'isolate> { + fn drop(&mut self) { + unsafe { v8_FreeNativeFunction(self.0.inner_val) } + } +} + +impl<'isolate_scope, 'isolate> From> + for LocalValueAny<'isolate_scope, 'isolate> +{ + fn from( + value: LocalNativeFunction<'isolate_scope, 'isolate>, + ) -> LocalValueAny<'isolate_scope, 'isolate> { + let inner_val = unsafe { v8_NativeFunctionToValue(value.0.inner_val) }; + LocalValueAny(ScopedValue { + inner_val, + isolate_scope: value.0.isolate_scope, + }) + } +} + +impl<'isolate_scope, 'isolate> From> + for Value<'isolate_scope, 'isolate> +{ + fn from( + value: LocalNativeFunction<'isolate_scope, 'isolate>, + ) -> Value<'isolate_scope, 'isolate> { + LocalValueAny::from(value).into() + } +} diff --git a/src/v8/types/native_function_template.rs b/src/v8/types/native_function_template.rs new file mode 100644 index 0000000..4c3555e --- /dev/null +++ b/src/v8/types/native_function_template.rs @@ -0,0 +1,223 @@ +/* + * Copyright Redis Ltd. 2022 - present + * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or + * the Server Side Public License v1 (SSPLv1). + */ +//! Contains the native function template facilities. + +use crate::v8_c_raw::bindings::v8_NewNativeFunctionTemplate; +use crate::v8_c_raw::bindings::{ + v8_ArgsGet, v8_ArgsGetSelf, v8_FreeNativeFunctionTemplate, v8_GetCurrentCtxRef, + v8_GetCurrentIsolate, v8_NativeFunctionTemplateToFunction, v8_local_native_function_template, + v8_local_value, v8_local_value_arr, +}; + +use std::os::raw::c_void; +use std::ptr; + +use crate::v8::context_scope::ContextScope; +use crate::v8::isolate::Isolate; +use crate::v8::isolate_scope::IsolateScope; +use crate::v8::types::native_function::LocalNativeFunction; +use crate::v8::types::LocalObject; +use crate::v8::types::ScopedValue; + +use super::any::LocalValueAny; +use super::Value; + +/// Native function template object. +/// +/// A [LocalNativeFunctionTemplate] is used to create functions at +/// runtime. There can only be one function created from a +/// [LocalNativeFunctionTemplate] in a context. The lifetime of the +/// created function is equal to the lifetime of the context. So in case +/// the embedder needs to create temporary functions that can be +/// collected using Scripts is preferred. +#[derive(Debug, Clone)] +pub struct LocalNativeFunctionTemplate<'isolate_scope, 'isolate>( + pub(crate) ScopedValue<'isolate_scope, 'isolate, v8_local_native_function_template>, +); + +impl<'isolate_scope, 'isolate> LocalNativeFunctionTemplate<'isolate_scope, 'isolate> { + /// Creates a new local native function template within the + /// provided [IsolateScope]. + pub fn new< + T: for<'d, 'e> Fn( + &LocalNativeFunctionArgs<'d, 'e>, + &'d IsolateScope<'e>, + &ContextScope<'d, 'e>, + ) -> Option>, + >( + function: T, + isolate_scope: &'isolate_scope IsolateScope<'isolate>, + ) -> Self { + let inner_val = unsafe { + v8_NewNativeFunctionTemplate( + isolate_scope.isolate.inner_isolate, + Some(native_basic_function::), + Box::into_raw(Box::new(function)).cast(), + Some(free_pd::), + ) + }; + + Self(ScopedValue { + inner_val, + isolate_scope, + }) + } +} + +/// An array of arguments for a [LocalNativeFunctionTemplate]. +pub struct LocalNativeFunctionArgs<'isolate_scope, 'isolate> { + pub(crate) inner_arr: *mut v8_local_value_arr, + len: usize, + isolate_scope: &'isolate_scope IsolateScope<'isolate>, +} + +pub(crate) extern "C" fn free_pd< + T: for<'d, 'c> Fn( + &LocalNativeFunctionArgs<'d, 'c>, + &'d IsolateScope<'c>, + &ContextScope<'d, 'c>, + ) -> Option>, +>( + pd: *mut c_void, +) { + unsafe { + let _ = Box::from_raw(pd.cast::()); + } +} + +pub(crate) extern "C" fn native_basic_function< + T: for<'d, 'c> Fn( + &LocalNativeFunctionArgs<'d, 'c>, + &'d IsolateScope<'c>, + &ContextScope<'d, 'c>, + ) -> Option>, +>( + args: *mut v8_local_value_arr, + len: usize, + pd: *mut c_void, +) -> *mut v8_local_value { + let func = unsafe { &*(pd.cast::()) }; + + let inner_isolate = unsafe { v8_GetCurrentIsolate(args) }; + let isolate = Isolate { + inner_isolate, + no_release: true, + }; + + let isolate_scope = IsolateScope::new(&isolate); + + let inner_ctx_ref = unsafe { v8_GetCurrentCtxRef(inner_isolate) }; + let ctx_scope = ContextScope { + inner_ctx_ref, + exit_on_drop: false, + isolate_scope: &isolate_scope, + }; + + let args = LocalNativeFunctionArgs { + inner_arr: args, + len, + isolate_scope: &isolate_scope, + }; + + let res = func(&args, &isolate_scope, &ctx_scope); + + match res { + Some(mut r) => { + let inner_val = r.0.inner_val; + r.0.inner_val = ptr::null_mut(); + inner_val + } + None => ptr::null_mut(), + } +} + +impl<'isolate_scope, 'isolate> LocalNativeFunctionTemplate<'isolate_scope, 'isolate> { + /// Builds (instantiates) a [LocalNativeFunction] out of this + /// template. + pub fn build(&self, ctx_scope: &ContextScope) -> LocalNativeFunction<'isolate_scope, 'isolate> { + let inner_val = unsafe { + v8_NativeFunctionTemplateToFunction(ctx_scope.inner_ctx_ref, self.0.inner_val) + }; + LocalNativeFunction(ScopedValue { + inner_val, + isolate_scope: self.0.isolate_scope, + }) + } +} + +impl<'isolate_scope, 'isolate> LocalNativeFunctionArgs<'isolate_scope, 'isolate> { + /// Return the i-th argument from the native function args + /// # Panics + #[must_use] + pub fn get(&self, i: usize) -> Value<'isolate_scope, 'isolate> { + assert!(i <= self.len); + let val = unsafe { v8_ArgsGet(self.inner_arr, i) }; + LocalValueAny(ScopedValue { + inner_val: val, + isolate_scope: self.isolate_scope, + }) + .into() + } + + /// Return the amount of arguments passed to the native function + #[must_use] + pub const fn len(&self) -> usize { + self.len + } + + /// Checks if the list of args is empty + #[must_use] + pub const fn is_empty(&self) -> bool { + self.len == 0 + } + + /// Checks if the list of args is empty + #[must_use] + pub fn get_self(&self) -> LocalObject<'isolate_scope, 'isolate> { + let inner_val = unsafe { v8_ArgsGetSelf(self.inner_arr) }; + LocalObject(ScopedValue { + inner_val, + isolate_scope: self.isolate_scope, + }) + } + + // pub const fn persist(&self) {} + + /// Returns an iterator [LocalNativeFunctionArgsIter] over the + /// function arguments. + pub fn iter<'a>(&'a self) -> LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a> { + LocalNativeFunctionArgsIter { + args: self, + index: 0, + } + } +} + +/// An iterator over the function arguments of a [LocalNativeFunction]. +pub struct LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a> { + args: &'a LocalNativeFunctionArgs<'isolate_scope, 'isolate>, + index: usize, +} + +impl<'isolate_scope, 'isolate, 'a> Iterator + for LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a> +{ + type Item = Value<'isolate_scope, 'isolate>; + fn next(&mut self) -> Option { + if self.index >= self.args.len() { + return None; + } + let res = self.args.get(self.index); + self.index += 1; + Some(res) + } +} + +impl<'isolate_scope, 'isolate> Drop for LocalNativeFunctionTemplate<'isolate_scope, 'isolate> { + fn drop(&mut self) { + unsafe { v8_FreeNativeFunctionTemplate(self.0.inner_val) } + } +} diff --git a/src/v8/types/object.rs b/src/v8/types/object.rs new file mode 100644 index 0000000..d334bdb --- /dev/null +++ b/src/v8/types/object.rs @@ -0,0 +1,203 @@ +/* + * Copyright Redis Ltd. 2022 - present + * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or + * the Server Side Public License v1 (SSPLv1). + */ +//! The JavaScript object facilities. + +use crate::v8_c_raw::bindings::{ + v8_FreeObject, v8_GetInternalFieldCount, v8_NewObject, v8_ObjectFreeze, v8_ObjectGet, + v8_ObjectGetInternalField, v8_ObjectSet, v8_ObjectSetInternalField, v8_ObjectToValue, + v8_ValueGetPropertyNames, v8_local_object, +}; + +use crate::v8::context_scope::ContextScope; +use crate::v8::isolate_scope::IsolateScope; +use crate::v8::types::native_function_template::LocalNativeFunctionArgs; +use crate::v8::types::LocalArray; +use crate::v8::types::ScopedValue; + +use super::string::LocalString; +use super::{LocalValueAny, Value}; + +/// A JavaScript object. +#[repr(transparent)] +#[derive(Debug, Clone)] +pub struct LocalObject<'isolate_scope, 'isolate>( + pub(crate) ScopedValue<'isolate_scope, 'isolate, v8_local_object>, +); + +impl<'isolate_scope, 'isolate> LocalObject<'isolate_scope, 'isolate> { + /// Creates a new local object within the provided [IsolateScope]. + pub fn new(isolate_scope: &'isolate_scope IsolateScope<'isolate>) -> Self { + let inner_val = unsafe { v8_NewObject(isolate_scope.isolate.inner_isolate) }; + Self(ScopedValue { + inner_val, + isolate_scope, + }) + } + + /// Returns the value of a given key. + #[must_use] + pub fn get( + &self, + ctx_scope: &ContextScope, + key: &LocalValueAny<'isolate_scope, 'isolate>, + ) -> Option> { + let inner_val = + unsafe { v8_ObjectGet(ctx_scope.inner_ctx_ref, self.0.inner_val, key.0.inner_val) }; + if inner_val.is_null() { + None + } else { + Some( + LocalValueAny(ScopedValue { + inner_val, + isolate_scope: self.0.isolate_scope, + }) + .into(), + ) + } + } + + /// Get value of a field by string. + #[must_use] + pub fn get_str_field( + &self, + ctx_scope: &ContextScope, + key: &str, + ) -> Option> { + let key: LocalString = self.0.isolate_scope.create_string(key).try_into().unwrap(); + let key = LocalValueAny::from(key); + self.get(ctx_scope, &key) + } + + /// Sets the value for the key within this [LocalObject]. + pub fn set(&self, ctx_scope: &ContextScope, key: &LocalValueAny, val: &LocalValueAny) { + unsafe { + v8_ObjectSet( + ctx_scope.inner_ctx_ref, + self.0.inner_val, + key.0.inner_val, + val.0.inner_val, + ) + }; + } + + /// Adds a method to this [LocalObject]. + pub fn set_native_function< + T: for<'d, 'e> Fn( + &LocalNativeFunctionArgs<'d, 'e>, + &'d IsolateScope<'e>, + &ContextScope<'d, 'e>, + ) -> Option>, + >( + &self, + ctx_scope: &ContextScope, + key: &str, + func: T, + ) { + let native_function = + LocalValueAny::try_from(ctx_scope.create_native_function(func)).unwrap(); + let name: LocalString = self.0.isolate_scope.create_string(key).try_into().unwrap(); + let name = LocalValueAny::from(name); + unsafe { + v8_ObjectSet( + ctx_scope.inner_ctx_ref, + self.0.inner_val, + name.0.inner_val, + native_function.0.inner_val, + ) + }; + } + + /// Sets the value in an internal field. + pub fn set_internal_field(&self, index: usize, val: &LocalValueAny) { + unsafe { v8_ObjectSetInternalField(self.0.inner_val, index, val.0.inner_val) }; + } + + /// Returns the value of an internal field accessible by the + /// provided `index`. + pub fn get_internal_field(&self, index: usize) -> Value<'isolate_scope, 'isolate> { + let inner_val = unsafe { v8_ObjectGetInternalField(self.0.inner_val, index) }; + LocalValueAny(ScopedValue { + inner_val, + isolate_scope: self.0.isolate_scope, + }) + .into() + } + + /// Returns the number of internal fields. + pub fn get_internal_field_count(&self) -> usize { + unsafe { v8_GetInternalFieldCount(self.0.inner_val) } + } + + /// Freezes this [LocalObject]. Freezing an object prevents + /// extensions and makes existing properties non-writable and + /// non-configurable. A frozen object can no longer be changed: + /// new properties cannot be added, existing properties cannot be + /// removed, their enumerability, configurability, writability, or + /// value cannot be changed, and the object's prototype cannot be + /// re-assigned. freeze() returns the same object that was passed in. + pub fn freeze(&self, ctx_scope: &ContextScope) { + unsafe { v8_ObjectFreeze(ctx_scope.inner_ctx_ref, self.0.inner_val) }; + } + + /// Convert the object into a generic JS value + pub fn get_property_names( + &self, + ctx_scope: &ContextScope, + ) -> LocalArray<'isolate_scope, 'isolate> { + let inner_val = + unsafe { v8_ValueGetPropertyNames(ctx_scope.inner_ctx_ref, self.0.inner_val) }; + LocalArray(ScopedValue { + inner_val, + isolate_scope: self.0.isolate_scope, + }) + } +} + +impl<'isolate_scope, 'isolate> Drop for LocalObject<'isolate_scope, 'isolate> { + fn drop(&mut self) { + unsafe { v8_FreeObject(self.0.inner_val) } + } +} + +impl<'isolate_scope, 'isolate> From> + for LocalValueAny<'isolate_scope, 'isolate> +{ + fn from(value: LocalObject<'isolate_scope, 'isolate>) -> Self { + let inner_val = unsafe { v8_ObjectToValue(value.0.inner_val) }; + LocalValueAny(ScopedValue { + inner_val, + isolate_scope: value.0.isolate_scope, + }) + } +} + +impl<'isolate_scope, 'isolate> TryFrom> + for LocalObject<'isolate_scope, 'isolate> +{ + type Error = &'static str; + + fn try_from(val: LocalValueAny<'isolate_scope, 'isolate>) -> Result { + if !val.is_object() { + return Err("Value is not an object"); + } + + Ok(unsafe { val.as_object() }) + } +} + +impl<'isolate_scope, 'isolate> TryFrom> + for LocalObject<'isolate_scope, 'isolate> +{ + type Error = &'static str; + + fn try_from(val: Value<'isolate_scope, 'isolate>) -> Result { + match val { + Value::Object(object) => Ok(object), + Value::Other(any) => Self::try_from(any), + _ => Err("Value is not an object"), + } + } +} diff --git a/src/v8/types/object_template.rs b/src/v8/types/object_template.rs new file mode 100644 index 0000000..f30fc24 --- /dev/null +++ b/src/v8/types/object_template.rs @@ -0,0 +1,163 @@ +/* + * Copyright Redis Ltd. 2022 - present + * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or + * the Server Side Public License v1 (SSPLv1). + */ +//! Contains the object template facilities. + +use crate::v8_c_raw::bindings::{ + v8_FreeObjectTemplate, v8_FreePersistedObjectTemplate, v8_NewObjectTemplate, + v8_ObjectTemplateNewInstance, v8_ObjectTemplatePersist, v8_ObjectTemplateSetFunction, + v8_ObjectTemplateSetInternalFieldCount, v8_ObjectTemplateSetObject, v8_ObjectTemplateSetValue, + v8_PersistedObjectTemplateToLocal, v8_local_object_template, v8_persisted_object_template, +}; + +use crate::v8::context_scope::ContextScope; +use crate::v8::isolate_scope::IsolateScope; +use crate::v8::types::native_function_template::{ + LocalNativeFunctionArgs, LocalNativeFunctionTemplate, +}; +use crate::v8::types::object::LocalObject; +use crate::v8::types::string::LocalString; +use crate::v8::types::ScopedValue; + +use super::any::LocalValueAny; + +/// The JavaScript object template, which is essentially a JavaScript +/// class builder: allows to set children objects (as in a map), add +/// member functions. +#[derive(Debug, Clone)] +pub struct LocalObjectTemplate<'isolate_scope, 'isolate>( + pub(crate) ScopedValue<'isolate_scope, 'isolate, v8_local_object_template>, +); + +impl<'isolate_scope, 'isolate> LocalObjectTemplate<'isolate_scope, 'isolate> { + /// Creates a new object template. + pub fn new(isolate_scope: &'isolate_scope IsolateScope<'isolate>) -> Self { + let inner_val = unsafe { v8_NewObjectTemplate(isolate_scope.isolate.inner_isolate) }; + LocalObjectTemplate(ScopedValue { + inner_val, + isolate_scope, + }) + } + /// Set a native function to the object template as a given key + pub fn set_native_function(&mut self, name: &LocalString, func: &LocalNativeFunctionTemplate) { + unsafe { + v8_ObjectTemplateSetFunction(self.0.inner_val, name.0.inner_val, func.0.inner_val) + }; + } + + /// Same as [set_native_function] but gets the key as &str and the + /// native function as closure. + pub fn add_native_function< + T: for<'d, 'e> Fn( + &LocalNativeFunctionArgs<'d, 'e>, + &'d IsolateScope<'e>, + &ContextScope<'d, 'e>, + ) -> Option>, + >( + &mut self, + name: &str, + func: T, + ) { + let native_func = self + .0 + .isolate_scope + .create_native_function_template(func) + .try_into() + .unwrap(); + let func_name = self.0.isolate_scope.create_string(name).try_into().unwrap(); + self.set_native_function(&func_name, &native_func); + } + + /// Set the given object to the object template on a given key + pub fn set_object(&mut self, name: &LocalString, obj: &Self) { + unsafe { v8_ObjectTemplateSetObject(self.0.inner_val, name.0.inner_val, obj.0.inner_val) }; + } + + /// Sets the number of internal fields for objects generated from + /// this template. + pub fn set_internal_field_count(&mut self, count: usize) { + unsafe { v8_ObjectTemplateSetInternalFieldCount(self.0.inner_val, count) }; + } + + /// Same as [set_object] but gets the key as an [str] slice. + pub fn add_object(&mut self, name: &str, obj: &Self) { + let obj_name = self.0.isolate_scope.create_string(name).try_into().unwrap(); + self.set_object(&obj_name, obj); + } + + /// Set a generic JS value into the object template as a given key + pub fn set_value(&mut self, name: &LocalString, obj: &LocalValueAny) { + unsafe { v8_ObjectTemplateSetValue(self.0.inner_val, name.0.inner_val, obj.0.inner_val) }; + } + + /// Same as [set_value] but gets the key as an [str] slice. + pub fn add_value(&mut self, name: &str, obj: &LocalValueAny) { + let val_name = self.0.isolate_scope.create_string(name).try_into().unwrap(); + self.set_value(&val_name, obj); + } + + /// Convert the object template into a generic JS value + #[must_use] + pub fn new_instance(&self, ctx_scope: &ContextScope) -> LocalObject<'isolate_scope, 'isolate> { + let inner_val = + unsafe { v8_ObjectTemplateNewInstance(ctx_scope.inner_ctx_ref, self.0.inner_val) }; + LocalObject(ScopedValue { + inner_val, + isolate_scope: self.0.isolate_scope, + }) + } + + /// Persists the [LocalObjectTemplate] by converting it into a + /// [PersistedObjectTemplate] which can outlive its [IsolateScope]. + pub fn persist(&self) -> PersistedObjectTemplate { + let inner_persist = unsafe { + v8_ObjectTemplatePersist(self.0.isolate_scope.isolate.inner_isolate, self.0.inner_val) + }; + PersistedObjectTemplate { + inner_persisted_obj_template: inner_persist, + } + } +} + +impl<'isolate_scope, 'isolate> Drop for LocalObjectTemplate<'isolate_scope, 'isolate> { + fn drop(&mut self) { + unsafe { v8_FreeObjectTemplate(self.0.inner_val) } + } +} + +/// The same as [LocalObjectTemplate] but not associated with an +/// [IsolateScope]. +pub struct PersistedObjectTemplate { + pub(crate) inner_persisted_obj_template: *mut v8_persisted_object_template, +} + +impl PersistedObjectTemplate { + /// Converts the [PersistedObjectTemplate] into a + /// [LocalObjectTemplate] within the provided [IsolateScope]. + pub fn to_local<'isolate_scope, 'isolate>( + &self, + isolate_scope: &'isolate_scope IsolateScope<'isolate>, + ) -> LocalObjectTemplate<'isolate_scope, 'isolate> { + let inner_val = unsafe { + v8_PersistedObjectTemplateToLocal( + isolate_scope.isolate.inner_isolate, + self.inner_persisted_obj_template, + ) + }; + LocalObjectTemplate(ScopedValue { + inner_val, + isolate_scope, + }) + } +} + +unsafe impl Sync for PersistedObjectTemplate {} +unsafe impl Send for PersistedObjectTemplate {} + +impl Drop for PersistedObjectTemplate { + fn drop(&mut self) { + unsafe { v8_FreePersistedObjectTemplate(self.inner_persisted_obj_template) } + } +} diff --git a/src/v8/types/persistent.rs b/src/v8/types/persistent.rs new file mode 100644 index 0000000..aa45bec --- /dev/null +++ b/src/v8/types/persistent.rs @@ -0,0 +1,92 @@ +/* + * Copyright Redis Ltd. 2022 - present + * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or + * the Server Side Public License v1 (SSPLv1). + */ +//! This is an abstraction over a persistent (non-scoped) V8 value. +//! As this is a fully abstract class in V8, the objects it can hold +//! can be of any javascript type. Hence it is generic. + +use crate::{ + v8::isolate_scope::IsolateScope, + v8_c_raw::bindings::{v8_FreePersistedValue, v8_PersistedValueToLocal, v8_persisted_value}, +}; + +use super::{any::LocalValueAny, ScopedValue}; + +/// JS generic persisted value +pub struct PersistValue { + pub(crate) inner_val: *mut v8_persisted_value, + forget: bool, +} + +impl PersistValue { + /// Converts the persisted value back to local value. + /// + /// # Panics + /// + /// Panics when the inner value is a null pointer. + #[must_use] + pub fn as_local<'isolate, 'isolate_scope>( + &self, + isolate_scope: &'isolate_scope IsolateScope<'isolate>, + ) -> LocalValueAny<'isolate_scope, 'isolate> { + assert!(!self.inner_val.is_null()); + let inner_val = unsafe { + v8_PersistedValueToLocal(isolate_scope.isolate.inner_isolate, self.inner_val) + }; + LocalValueAny(ScopedValue { + inner_val, + isolate_scope, + }) + } + + /// Disables the [Drop] implementation for this object, marking it + /// as a non-requiring to be dropped. + /// + /// # Panics + /// + /// Panics when the inner value is a null pointer. + pub fn forget(&mut self) { + assert!(!self.inner_val.is_null()); + self.forget = true; + } + + /// Consumes the current persistent value and attempts to convert it + /// into a local value. The object isn't dropped. + /// + /// # Panics + /// + /// Panics when the inner value is a null pointer, see [Self::as_local]. + pub fn take_local<'isolate, 'isolate_scope>( + mut self, + isolate_scope: &'isolate_scope IsolateScope<'isolate>, + ) -> LocalValueAny<'isolate_scope, 'isolate> { + let val = self.as_local(isolate_scope); + unsafe { v8_FreePersistedValue(self.inner_val) } + self.forget(); + self.inner_val = std::ptr::null_mut(); + val + } +} + +impl From<*mut v8_persisted_value> for PersistValue { + fn from(value: *mut v8_persisted_value) -> Self { + Self { + inner_val: value, + forget: false, + } + } +} + +unsafe impl Sync for PersistValue {} +unsafe impl Send for PersistValue {} + +impl Drop for PersistValue { + fn drop(&mut self) { + if self.forget { + return; + } + unsafe { v8_FreePersistedValue(self.inner_val) } + } +} diff --git a/src/v8/types/promise.rs b/src/v8/types/promise.rs new file mode 100644 index 0000000..fddcf21 --- /dev/null +++ b/src/v8/types/promise.rs @@ -0,0 +1,111 @@ +/* + * Copyright Redis Ltd. 2022 - present + * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or + * the Server Side Public License v1 (SSPLv1). + */ +//! Contains the JavaScript promise facilities. + +use crate::v8_c_raw::bindings::{ + v8_FreePromise, v8_PromiseGetResult, v8_PromiseGetState, + v8_PromiseState_v8_PromiseState_Fulfilled, v8_PromiseState_v8_PromiseState_Pending, + v8_PromiseState_v8_PromiseState_Rejected, v8_PromiseThen, v8_PromiseToValue, v8_local_promise, +}; + +use crate::v8::context_scope::ContextScope; +use crate::v8::types::native_function::LocalNativeFunction; +use crate::v8::types::ScopedValue; + +use super::any::LocalValueAny; + +/// A promise is an object representing an eventual completion +/// (successful or not) of an asynchronous operation and its resulting +/// value. +/// +/// See [Mozilla Documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). +pub struct LocalPromise<'isolate_scope, 'isolate>( + pub(crate) ScopedValue<'isolate_scope, 'isolate, v8_local_promise>, +); + +/// The states a [LocalPromise] can be in. +#[derive(Debug, PartialEq)] +pub enum PromiseState { + /// The operation has completed successfully. + Fulfilled, + /// The operation has failed. + Rejected, + /// The initial state of a promise - pending execution. + Pending, +} + +impl<'isolate_scope, 'isolate> LocalPromise<'isolate_scope, 'isolate> { + /// Set resolve and reject callbacks. + pub fn then( + &self, + ctx: &ContextScope, + resolve: &LocalNativeFunction, + reject: &LocalNativeFunction, + ) { + unsafe { + v8_PromiseThen( + self.0.inner_val, + ctx.inner_ctx_ref, + resolve.0.inner_val, + reject.0.inner_val, + ); + }; + } + + /// Return the state on the promise object. + #[allow(non_upper_case_globals)] + pub fn state(&self) -> Option { + let inner_state = unsafe { v8_PromiseGetState(self.0.inner_val) }; + Some(match inner_state { + v8_PromiseState_v8_PromiseState_Fulfilled => PromiseState::Fulfilled, + v8_PromiseState_v8_PromiseState_Rejected => PromiseState::Rejected, + v8_PromiseState_v8_PromiseState_Pending => PromiseState::Pending, + _ => return None, + }) + } + + /// Return the result of the promise object. + /// Only applicable if the promise object was resolved/rejected. + pub fn get_result(&self) -> LocalValueAny<'isolate_scope, 'isolate> { + let inner_val = unsafe { v8_PromiseGetResult(self.0.inner_val) }; + LocalValueAny(ScopedValue { + inner_val, + isolate_scope: self.0.isolate_scope, + }) + } +} + +impl<'isolate_scope, 'isolate> Drop for LocalPromise<'isolate_scope, 'isolate> { + fn drop(&mut self) { + unsafe { v8_FreePromise(self.0.inner_val) } + } +} + +impl<'isolate_scope, 'isolate> From> + for LocalValueAny<'isolate_scope, 'isolate> +{ + fn from(value: LocalPromise<'isolate_scope, 'isolate>) -> Self { + let inner_val = unsafe { v8_PromiseToValue(value.0.inner_val) }; + Self(ScopedValue { + inner_val, + isolate_scope: value.0.isolate_scope, + }) + } +} + +impl<'isolate_scope, 'isolate> TryFrom> + for LocalPromise<'isolate_scope, 'isolate> +{ + type Error = &'static str; + + fn try_from(value: LocalValueAny<'isolate_scope, 'isolate>) -> Result { + if value.is_promise() { + Ok(unsafe { value.as_promise() }) + } else { + Err("The value is not a promise.") + } + } +} diff --git a/src/v8/types/resolver.rs b/src/v8/types/resolver.rs new file mode 100644 index 0000000..fa165a3 --- /dev/null +++ b/src/v8/types/resolver.rs @@ -0,0 +1,64 @@ +/* + * Copyright Redis Ltd. 2022 - present + * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or + * the Server Side Public License v1 (SSPLv1). + */ +//! This module contains the promise resolution facilities. + +use crate::v8_c_raw::bindings::{ + v8_FreeResolver, v8_ResolverGetPromise, v8_ResolverReject, v8_ResolverResolve, + v8_ResolverToValue, v8_local_resolver, +}; + +use crate::v8::context_scope::ContextScope; +use crate::v8::types::promise::LocalPromise; +use crate::v8::types::ScopedValue; + +use super::any::LocalValueAny; + +// TODO it would be nice to associate a promise with the resolver, for +// example by lifetimes and a phantomguard or by a real object, to +// ensure that one promise is always associated with one, same promise. +/// A resolver for the [LocalPromise]. +pub struct LocalPromiseResolver<'isolate_scope, 'isolate>( + pub(crate) ScopedValue<'isolate_scope, 'isolate, v8_local_resolver>, +); + +impl<'isolate_scope, 'isolate> LocalPromiseResolver<'isolate_scope, 'isolate> { + /// Returns the promise object assosiated with this resolver. + pub fn get_promise(&self) -> LocalPromise<'isolate_scope, 'isolate> { + let inner_val = unsafe { v8_ResolverGetPromise(self.0.inner_val) }; + LocalPromise(ScopedValue { + inner_val, + isolate_scope: self.0.isolate_scope, + }) + } + + /// Resolves the associated promise with the given JavaScript value. + pub fn resolve(&self, ctx_scope: &ContextScope, val: &LocalValueAny) { + unsafe { v8_ResolverResolve(ctx_scope.inner_ctx_ref, self.0.inner_val, val.0.inner_val) }; + } + + /// Rejects the associated promise with the given JavaScript value. + pub fn reject(&self, ctx_scope: &ContextScope, val: &LocalValueAny) { + unsafe { v8_ResolverReject(ctx_scope.inner_ctx_ref, self.0.inner_val, val.0.inner_val) }; + } +} + +impl<'isolate_scope, 'isolate> Drop for LocalPromiseResolver<'isolate_scope, 'isolate> { + fn drop(&mut self) { + unsafe { v8_FreeResolver(self.0.inner_val) } + } +} + +impl<'isolate_scope, 'isolate> From> + for LocalValueAny<'isolate_scope, 'isolate> +{ + fn from(value: LocalPromiseResolver<'isolate_scope, 'isolate>) -> Self { + let inner_val = unsafe { v8_ResolverToValue(value.0.inner_val) }; + Self(ScopedValue { + inner_val, + isolate_scope: value.0.isolate_scope, + }) + } +} diff --git a/src/v8/types/script.rs b/src/v8/types/script.rs new file mode 100644 index 0000000..e3d81e2 --- /dev/null +++ b/src/v8/types/script.rs @@ -0,0 +1,94 @@ +/* + * Copyright Redis Ltd. 2022 - present + * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or + * the Server Side Public License v1 (SSPLv1). + */ +//! The JavaScript script facilities. + +use crate::v8_c_raw::bindings::{ + v8_FreePersistedScript, v8_FreeScript, v8_PersistedScriptToLocal, v8_Run, v8_ScriptPersist, + v8_local_script, v8_persisted_script, +}; + +use crate::v8::context_scope::ContextScope; +use crate::v8::isolate_scope::IsolateScope; +use crate::v8::types::ScopedValue; + +use super::any::LocalValueAny; +use super::Value; + +/// A JavaScript script object. +pub struct LocalScript<'isolate_scope, 'isolate>( + pub(crate) ScopedValue<'isolate_scope, 'isolate, v8_local_script>, +); + +/// A persisted script is script that isn't tied to the [IsolateScope]'s +/// lifetime it was created for. +pub struct PersistedScript { + pub(crate) inner_persisted_script: *mut v8_persisted_script, +} + +impl<'isolate_scope, 'isolate> LocalScript<'isolate_scope, 'isolate> { + /// Run the script + #[must_use] + pub fn run(&self, ctx: &ContextScope) -> Option> { + let inner_val = unsafe { v8_Run(ctx.inner_ctx_ref, self.0.inner_val) }; + if inner_val.is_null() { + None + } else { + Some( + LocalValueAny(ScopedValue { + inner_val, + isolate_scope: self.0.isolate_scope, + }) + .into(), + ) + } + } + + /// Persists the [LocalScript] so that it can outlive the + /// [IsolateScope] it was tied to. + pub fn persist(&self) -> PersistedScript { + let inner_persisted_script = unsafe { + v8_ScriptPersist(self.0.isolate_scope.isolate.inner_isolate, self.0.inner_val) + }; + PersistedScript { + inner_persisted_script, + } + } +} + +impl PersistedScript { + /// Converts the [PersistedScript] into a [LocalScript] for the + /// provided [IsolateScope]. + pub fn to_local<'isolate_scope, 'isolate>( + &self, + isolate_scope: &'isolate_scope IsolateScope<'isolate>, + ) -> LocalScript<'isolate_scope, 'isolate> { + let inner_val = unsafe { + v8_PersistedScriptToLocal( + isolate_scope.isolate.inner_isolate, + self.inner_persisted_script, + ) + }; + LocalScript(ScopedValue { + inner_val, + isolate_scope, + }) + } +} + +impl<'isolate_scope, 'isolate> Drop for LocalScript<'isolate_scope, 'isolate> { + fn drop(&mut self) { + unsafe { v8_FreeScript(self.0.inner_val) } + } +} + +unsafe impl Sync for PersistedScript {} +unsafe impl Send for PersistedScript {} + +impl Drop for PersistedScript { + fn drop(&mut self) { + unsafe { v8_FreePersistedScript(self.inner_persisted_script) } + } +} diff --git a/src/v8/types/set.rs b/src/v8/types/set.rs new file mode 100644 index 0000000..67a9691 --- /dev/null +++ b/src/v8/types/set.rs @@ -0,0 +1,83 @@ +/* + * Copyright Redis Ltd. 2022 - present + * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or + * the Server Side Public License v1 (SSPLv1). + */ +//! Contains the JavaScript set facilities. + +use crate::v8_c_raw::bindings::{v8_FreeSet, v8_NewSet, v8_SetAdd, v8_SetToValue, v8_local_set}; + +use crate::v8::context_scope::ContextScope; +use crate::v8::isolate_scope::IsolateScope; +use crate::v8::types::ScopedValue; + +use super::any::LocalValueAny; +use super::Value; + +/// A javascript set. +#[derive(Debug, Clone)] +pub struct LocalSet<'isolate_scope, 'isolate>( + pub(crate) ScopedValue<'isolate_scope, 'isolate, v8_local_set>, +); + +impl<'isolate_scope, 'isolate> LocalSet<'isolate_scope, 'isolate> { + /// Creates a new local set for the passed [IsolateScope]. + pub fn new(isolate_scope: &'isolate_scope IsolateScope<'isolate>) -> Self { + let inner_val = unsafe { v8_NewSet(isolate_scope.isolate.inner_isolate) }; + Self(ScopedValue { + inner_val, + isolate_scope, + }) + } + + /// Adds new element to the set. + pub fn add(&self, ctx_scope: &ContextScope, val: &LocalValueAny) { + unsafe { v8_SetAdd(ctx_scope.inner_ctx_ref, self.0.inner_val, val.0.inner_val) }; + } +} + +impl<'isolate_scope, 'isolate> Drop for LocalSet<'isolate_scope, 'isolate> { + fn drop(&mut self) { + unsafe { v8_FreeSet(self.0.inner_val) } + } +} + +impl<'isolate_scope, 'isolate> From> + for LocalValueAny<'isolate_scope, 'isolate> +{ + fn from(value: LocalSet<'isolate_scope, 'isolate>) -> Self { + let inner_val = unsafe { v8_SetToValue(value.0.inner_val) }; + LocalValueAny(ScopedValue { + inner_val, + isolate_scope: value.0.isolate_scope, + }) + } +} + +impl<'isolate_scope, 'isolate> TryFrom> + for LocalSet<'isolate_scope, 'isolate> +{ + type Error = &'static str; + + fn try_from(val: LocalValueAny<'isolate_scope, 'isolate>) -> Result { + if !val.is_set() { + return Err("Value is not a set"); + } + + Ok(unsafe { val.as_set() }) + } +} + +impl<'isolate_scope, 'isolate> TryFrom> + for LocalSet<'isolate_scope, 'isolate> +{ + type Error = &'static str; + + fn try_from(val: Value<'isolate_scope, 'isolate>) -> Result { + match val { + Value::Set(set) => Ok(set), + Value::Other(any) => any.try_into(), + _ => Err("Value is not a set"), + } + } +} diff --git a/src/v8/types/string.rs b/src/v8/types/string.rs new file mode 100644 index 0000000..2efc67a --- /dev/null +++ b/src/v8/types/string.rs @@ -0,0 +1,89 @@ +/* + * Copyright Redis Ltd. 2022 - present + * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or + * the Server Side Public License v1 (SSPLv1). + */ +//! The JavaScript string facilities. + +use crate::v8_c_raw::bindings::{ + v8_FreeString, v8_NewString, v8_StringToStringObject, v8_StringToValue, v8_local_string, +}; + +use crate::v8::isolate_scope::IsolateScope; +use crate::v8::types::object::LocalObject; +use crate::v8::types::ScopedValue; + +use super::any::LocalValueAny; + +/// A JavaScript string object. +#[derive(Debug, Clone)] +pub struct LocalString<'isolate_scope, 'isolate>( + pub(crate) ScopedValue<'isolate_scope, 'isolate, v8_local_string>, +); + +impl<'isolate_scope, 'isolate> LocalString<'isolate_scope, 'isolate> { + /// Creates a new JavaScript string within the passed [IsolateScope]. + pub fn new(s: &str, isolate_scope: &'isolate_scope IsolateScope<'isolate>) -> Self { + let inner_val = unsafe { + v8_NewString( + isolate_scope.isolate.inner_isolate, + s.as_ptr().cast(), + s.len(), + ) + }; + + Self(ScopedValue { + inner_val, + isolate_scope, + }) + } +} + +impl<'isolate_scope, 'isolate> Drop for LocalString<'isolate_scope, 'isolate> { + fn drop(&mut self) { + unsafe { v8_FreeString(self.0.inner_val) } + } +} + +impl<'isolate_scope, 'isolate> From> + for LocalValueAny<'isolate_scope, 'isolate> +{ + fn from(value: LocalString<'isolate_scope, 'isolate>) -> Self { + let inner_val = unsafe { v8_StringToValue(value.0.inner_val) }; + LocalValueAny(ScopedValue { + inner_val, + isolate_scope: value.0.isolate_scope, + }) + } +} + +impl<'isolate_scope, 'isolate> From> + for LocalObject<'isolate_scope, 'isolate> +{ + fn from(value: LocalString<'isolate_scope, 'isolate>) -> Self { + let inner_val = unsafe { + v8_StringToStringObject( + value.0.isolate_scope.isolate.inner_isolate, + value.0.inner_val, + ) + }; + LocalObject(ScopedValue { + inner_val, + isolate_scope: value.0.isolate_scope, + }) + } +} + +impl<'isolate_scope, 'isolate> TryFrom> + for LocalString<'isolate_scope, 'isolate> +{ + type Error = &'static str; + + fn try_from(val: LocalValueAny<'isolate_scope, 'isolate>) -> Result { + if !val.is_string() { + return Err("Value is not a string"); + } + + Ok(unsafe { val.as_string() }) + } +} diff --git a/src/v8/types/try_catch.rs b/src/v8/types/try_catch.rs new file mode 100644 index 0000000..2cd5342 --- /dev/null +++ b/src/v8/types/try_catch.rs @@ -0,0 +1,79 @@ +/* + * Copyright Redis Ltd. 2022 - present + * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or + * the Server Side Public License v1 (SSPLv1). + */ +//! Contains the `try-catch` facilities for JavaScript. It is possible +//! to assign a custom, external Rust-based exception handler via +//! [TryCatch]. + +use crate::v8::isolate_scope::IsolateScope; +use crate::v8_c_raw::bindings::{ + v8_FreeTryCatch, v8_NewTryCatch, v8_TryCatchGetException, v8_TryCatchGetTrace, + v8_TryCatchHasTerminated, v8_trycatch, +}; + +use crate::v8::context_scope::ContextScope; +use crate::v8::types::ScopedValue; + +use super::any::LocalValueAny; + +/// An object that is responsible to catch any exception which raised +/// during the JS code invocation. +#[derive(Debug, Clone)] +pub struct TryCatch<'isolate_scope, 'isolate>( + pub(crate) ScopedValue<'isolate_scope, 'isolate, v8_trycatch>, +); + +impl<'isolate_scope, 'isolate> TryCatch<'isolate_scope, 'isolate> { + /// Creates a new JavaScript try-catch within the passed [IsolateScope]. + pub fn new(isolate_scope: &'isolate_scope IsolateScope<'isolate>) -> Self { + let inner_val = unsafe { v8_NewTryCatch(isolate_scope.isolate.inner_isolate) }; + + Self(ScopedValue { + inner_val, + isolate_scope, + }) + } + + /// Return the exception that was raise during the JS code invocation. + /// + /// # Panics + /// + /// Panics when the exception pointer is null. + pub fn get_exception(&self) -> LocalValueAny<'isolate_scope, 'isolate> { + let inner_val = unsafe { v8_TryCatchGetException(self.0.inner_val) }; + assert!(!inner_val.is_null()); + LocalValueAny(ScopedValue { + inner_val, + isolate_scope: self.0.isolate_scope, + }) + } + + /// Return the trace of the catch exception, the function returns Option because trace is not always provided. + pub fn get_trace( + &self, + ctx_scope: &ContextScope, + ) -> Option> { + let inner_val = unsafe { v8_TryCatchGetTrace(self.0.inner_val, ctx_scope.inner_ctx_ref) }; + if inner_val.is_null() { + return None; + } + Some(LocalValueAny(ScopedValue { + inner_val, + isolate_scope: self.0.isolate_scope, + })) + } + + /// Returns `true` if the try_catch has terminated. + pub fn has_terminated(&self) -> bool { + let res = unsafe { v8_TryCatchHasTerminated(self.0.inner_val) }; + res > 0 + } +} + +impl<'isolate_scope, 'isolate> Drop for TryCatch<'isolate_scope, 'isolate> { + fn drop(&mut self) { + unsafe { v8_FreeTryCatch(self.0.inner_val) } + } +} diff --git a/src/v8/types/unlocker.rs b/src/v8/types/unlocker.rs new file mode 100644 index 0000000..3b6b21a --- /dev/null +++ b/src/v8/types/unlocker.rs @@ -0,0 +1,53 @@ +/* + * Copyright Redis Ltd. 2022 - present + * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or + * the Server Side Public License v1 (SSPLv1). + */ +//! Contains the thread-safe facilities. +//! +//! From [https://v8.github.io/api/head/classv8_1_1Unlocker.html#aa6789fe804cc059d9554c7ae7958c440]: +//! +//! > Multiple threads in V8 are allowed, but only one thread at a time +//! > is allowed to use any given V8 isolate, see the comments in the +//! > Isolate class. The definition of 'using a V8 isolate' includes +//! > accessing handles or holding onto object pointers obtained from V8 +//! > handles while in the particular V8 isolate. It is up to the user +//! > of V8 to ensure, perhaps with locking, that this constraint is not +//! > violated. In addition to any other synchronization mechanism that +//! > may be used, the v8::Locker and v8::Unlocker classes must be used +//! > to signal thread switches to V8. +//! > +//! > v8::Locker is a scoped lock object. While it's active, i.e. +//! > between its construction and destruction, the current thread is +//! > allowed to use the locked isolate. V8 guarantees that an isolate +//! > can be locked by at most one thread at any time. In other words, +//! > the scope of a v8::Locker is a critical section. + +use crate::v8::isolate_scope::IsolateScope; +use crate::v8_c_raw::bindings::{v8_FreeUnlocker, v8_NewUnlocker, v8_unlocker}; + +use super::ScopedValue; + +/// Releases the threads locked by the `Locker`, for example a +/// long-running callback from V8, to enable other threads to work. +#[derive(Debug, Clone)] +pub struct Unlocker<'isolate_scope, 'isolate>( + pub(crate) ScopedValue<'isolate_scope, 'isolate, v8_unlocker>, +); + +impl<'isolate_scope, 'isolate> Unlocker<'isolate_scope, 'isolate> { + /// Creates a new [Unlocker] within the provided [IsolateScope]. + pub fn new(isolate_scope: &'isolate_scope IsolateScope<'isolate>) -> Self { + let inner_val = unsafe { v8_NewUnlocker(isolate_scope.isolate.inner_isolate) }; + Self(ScopedValue { + inner_val, + isolate_scope, + }) + } +} + +impl<'isolate_scope, 'isolate> Drop for Unlocker<'isolate_scope, 'isolate> { + fn drop(&mut self) { + unsafe { v8_FreeUnlocker(self.0.inner_val) }; + } +} diff --git a/src/v8/types/utf8.rs b/src/v8/types/utf8.rs new file mode 100644 index 0000000..a306c2c --- /dev/null +++ b/src/v8/types/utf8.rs @@ -0,0 +1,94 @@ +/* + * Copyright Redis Ltd. 2022 - present + * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or + * the Server Side Public License v1 (SSPLv1). + */ +//! Contains the UTF-8 object representation facilities, represented by +//! the [LocalUtf8] type. + +use crate::v8::types::ScopedValue; +use crate::v8_c_raw::bindings::{v8_FreeUtf8, v8_ToUtf8, v8_Utf8PtrLen, v8_utf8_value}; + +use std::ptr::NonNull; +use std::slice; +use std::str; + +use super::any::LocalValueAny; +use super::string::LocalString; + +/// An UTF-8 string representation of an object. +pub struct LocalUtf8<'isolate_scope, 'isolate>( + pub(crate) ScopedValue<'isolate_scope, 'isolate, v8_utf8_value>, +); + +impl<'isolate_scope, 'isolate> LocalUtf8<'isolate_scope, 'isolate> { + /// Get a [str] slice from the object. + /// + /// # Panics + /// + /// Panics when the [std::str::from_utf8] fails. + pub fn as_str(&self) -> &str { + let mut len: usize = 0; + let buff = unsafe { v8_Utf8PtrLen(self.0.inner_val, &mut len) }; + let bytes = unsafe { slice::from_raw_parts(buff.cast(), len) }; + str::from_utf8(bytes).expect("Couldn't create a string") + } +} + +impl<'isolate_scope, 'isolate> AsRef for LocalUtf8<'isolate_scope, 'isolate> { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl<'isolate_scope, 'isolate> Drop for LocalUtf8<'isolate_scope, 'isolate> { + fn drop(&mut self) { + unsafe { v8_FreeUtf8(self.0.inner_val) } + } +} + +impl<'isolate_scope, 'isolate> From> + for LocalUtf8<'isolate_scope, 'isolate> +{ + fn from(value: LocalString<'isolate_scope, 'isolate>) -> Self { + let value = LocalValueAny::from(value); + // Note that given the implementation of the `TryFrom` below, + // this code should never fail as we always check for the actual + // type of the value stored within the `LocalValueAny` to be + // a string or a string object, and since in this case it was + // a string and we knew that fact, it must never fail. + Self::try_from(value).expect("Failed to convert a string to Utf8.") + } +} + +impl<'isolate_scope, 'isolate> TryFrom> + for LocalUtf8<'isolate_scope, 'isolate> +{ + type Error = &'static str; + + fn try_from(value: LocalValueAny<'isolate_scope, 'isolate>) -> Result { + if !value.is_string() && !value.is_string_object() { + return Err("Value is not string"); + } + + NonNull::new(unsafe { + v8_ToUtf8( + value.0.isolate_scope.isolate.inner_isolate, + value.0.inner_val, + ) + }) + .map(|ptr| { + LocalUtf8(ScopedValue { + inner_val: ptr.as_ptr(), + isolate_scope: value.0.isolate_scope, + }) + }) + .ok_or("Failed converting to utf8") + } +} + +impl<'isolate_scope, 'isolate> From> for String { + fn from(value: LocalUtf8<'isolate_scope, 'isolate>) -> Self { + value.as_str().to_owned() + } +} diff --git a/src/v8/v8_array.rs b/src/v8/v8_array.rs deleted file mode 100644 index 44efc5c..0000000 --- a/src/v8/v8_array.rs +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright Redis Ltd. 2022 - present - * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or - * the Server Side Public License v1 (SSPLv1). - */ - -use crate::v8_c_raw::bindings::{ - v8_ArrayGet, v8_ArrayLen, v8_ArrayToValue, v8_FreeArray, v8_local_array, -}; - -use crate::v8::isolate_scope::V8IsolateScope; -use crate::v8::v8_context_scope::V8ContextScope; -use crate::v8::v8_value::V8LocalValue; - -/// JS object -pub struct V8LocalArray<'isolate_scope, 'isolate> { - pub(crate) inner_array: *mut v8_local_array, - pub(crate) isolate_scope: &'isolate_scope V8IsolateScope<'isolate>, -} - -impl<'isolate_scope, 'isolate> V8LocalArray<'isolate_scope, 'isolate> { - /// Returns the length of the array. - pub fn len(&self) -> usize { - unsafe { v8_ArrayLen(self.inner_array) } - } - - /// Returns true if the array is empty. - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Returns an iterator to the array's objects. - pub fn iter<'array, 'context_scope>( - &'array self, - context_scope: &'context_scope V8ContextScope<'isolate_scope, 'isolate>, - ) -> V8LocalArrayIterator<'context_scope, 'array, 'isolate_scope, 'isolate> { - V8LocalArrayIterator { - index: 0, - array: self, - context: context_scope, - } - } - - /// Returns a single object stored within the array. - pub fn get( - &self, - ctx_scope: &V8ContextScope, - index: usize, - ) -> V8LocalValue<'isolate_scope, 'isolate> { - let inner_val = unsafe { v8_ArrayGet(ctx_scope.inner_ctx_ref, self.inner_array, index) }; - V8LocalValue { - inner_val, - isolate_scope: self.isolate_scope, - } - } - - /// Converts the array to a value object. - pub fn to_value(&self) -> V8LocalValue<'isolate_scope, 'isolate> { - let inner_val = unsafe { v8_ArrayToValue(self.inner_array) }; - V8LocalValue { - inner_val, - isolate_scope: self.isolate_scope, - } - } -} - -impl<'isolate_scope, 'isolate> From> - for V8LocalValue<'isolate_scope, 'isolate> -{ - fn from(array: V8LocalArray<'isolate_scope, 'isolate>) -> Self { - array.to_value() - } -} - -/// An iterator over the objects stored within the [`V8LocalArray`]. -pub struct V8LocalArrayIterator<'context_scope, 'array, 'isolate_scope, 'isolate> { - index: usize, - array: &'array V8LocalArray<'isolate_scope, 'isolate>, - context: &'context_scope V8ContextScope<'isolate_scope, 'isolate>, -} - -impl<'context_scope, 'array, 'isolate_scope, 'isolate> Iterator - for V8LocalArrayIterator<'context_scope, 'array, 'isolate_scope, 'isolate> -{ - type Item = V8LocalValue<'isolate_scope, 'isolate>; - - fn next(&mut self) -> Option { - if self.index >= self.array.len() { - return None; - } - - let value = self.array.get(self.context, self.index); - self.index += 1; - Some(value) - } -} - -impl<'isolate_scope, 'isolate> Drop for V8LocalArray<'isolate_scope, 'isolate> { - fn drop(&mut self) { - unsafe { v8_FreeArray(self.inner_array) } - } -} - -impl<'isolate_scope, 'isolate> TryFrom> - for V8LocalArray<'isolate_scope, 'isolate> -{ - type Error = &'static str; - - fn try_from(val: V8LocalValue<'isolate_scope, 'isolate>) -> Result { - if !val.is_array() { - return Err("Value is not an array"); - } - - Ok(val.as_array()) - } -} diff --git a/src/v8/v8_array_buffer.rs b/src/v8/v8_array_buffer.rs deleted file mode 100644 index 6cf5eca..0000000 --- a/src/v8/v8_array_buffer.rs +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright Redis Ltd. 2022 - present - * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or - * the Server Side Public License v1 (SSPLv1). - */ - -use crate::v8_c_raw::bindings::{ - v8_ArrayBufferGetData, v8_ArrayBufferToValue, v8_FreeArrayBuffer, v8_local_array_buff, -}; - -use crate::v8::isolate_scope::V8IsolateScope; -use crate::v8::v8_value::V8LocalValue; - -/// JS object -pub struct V8LocalArrayBuffer<'isolate_scope, 'isolate> { - pub(crate) inner_array_buffer: *mut v8_local_array_buff, - pub(crate) isolate_scope: &'isolate_scope V8IsolateScope<'isolate>, -} - -impl<'isolate_scope, 'isolate> V8LocalArrayBuffer<'isolate_scope, 'isolate> { - pub fn data(&self) -> &[u8] { - let mut size = 0; - let data = - unsafe { v8_ArrayBufferGetData(self.inner_array_buffer, &mut size as *mut usize) }; - unsafe { std::slice::from_raw_parts(data.cast::(), size) } - } - - pub fn to_value(&self) -> V8LocalValue<'isolate_scope, 'isolate> { - let inner_val = unsafe { v8_ArrayBufferToValue(self.inner_array_buffer) }; - V8LocalValue { - inner_val, - isolate_scope: self.isolate_scope, - } - } -} - -impl<'isolate_scope, 'isolate> Drop for V8LocalArrayBuffer<'isolate_scope, 'isolate> { - fn drop(&mut self) { - unsafe { v8_FreeArrayBuffer(self.inner_array_buffer) } - } -} - -impl<'isolate_scope, 'isolate> TryFrom> - for V8LocalArrayBuffer<'isolate_scope, 'isolate> -{ - type Error = &'static str; - fn try_from(val: V8LocalValue<'isolate_scope, 'isolate>) -> Result { - if !val.is_array_buffer() { - return Err("Value is not an array buffer"); - } - - Ok(val.as_array_buffer()) - } -} diff --git a/src/v8/v8_external_data.rs b/src/v8/v8_external_data.rs deleted file mode 100644 index ab2fe7e..0000000 --- a/src/v8/v8_external_data.rs +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Redis Ltd. 2022 - present - * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or - * the Server Side Public License v1 (SSPLv1). - */ - -use crate::v8_c_raw::bindings::{ - v8_ExternalDataGet, v8_ExternalDataToValue, v8_local_external_data, -}; - -use crate::v8::isolate_scope::V8IsolateScope; -use crate::v8::v8_value::V8LocalValue; - -/// JS object -pub struct V8LocalExternalData<'isolate_scope, 'isolate> { - pub(crate) inner_ext: *mut v8_local_external_data, - pub(crate) isolate_scope: &'isolate_scope V8IsolateScope<'isolate>, -} - -impl<'isolate_scope, 'isolate> V8LocalExternalData<'isolate_scope, 'isolate> { - /// Convert the object into a generic JS value - #[must_use] - pub fn to_value(&self) -> V8LocalValue<'isolate_scope, 'isolate> { - let inner_val = unsafe { v8_ExternalDataToValue(self.inner_ext) }; - V8LocalValue { - inner_val, - isolate_scope: self.isolate_scope, - } - } - - pub fn get_data(&self) -> &'isolate_scope T { - unsafe { &*(v8_ExternalDataGet(self.inner_ext) as *const T) } - } - - pub fn get_data_mut(&mut self) -> &mut T { - unsafe { &mut *(v8_ExternalDataGet(self.inner_ext) as *mut T) } - } -} diff --git a/src/v8/v8_module.rs b/src/v8/v8_module.rs deleted file mode 100644 index 6279019..0000000 --- a/src/v8/v8_module.rs +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright Redis Ltd. 2022 - present - * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or - * the Server Side Public License v1 (SSPLv1). - */ - -use crate::v8_c_raw::bindings::{ - v8_ContextRefGetIsolate, v8_EvaluateModule, v8_FreeModule, v8_FreePersistedModule, - v8_InitiateModule, v8_ModuleGetIdentityHash, v8_ModulePersist, v8_ModuleToLocal, - v8_context_ref, v8_local_module, v8_local_string, v8_persisted_module, -}; -use crate::RawIndex; - -use crate::v8::isolate::V8Isolate; -use crate::v8::isolate_scope::V8IsolateScope; -use crate::v8::v8_context_scope::V8ContextScope; -use crate::v8::v8_string::V8LocalString; -use crate::v8::v8_value::V8LocalValue; -use std::os::raw::c_int; -use std::ptr; - -/// JS script object -pub struct V8LocalModule<'isolate_scope, 'isolate> { - pub(crate) inner_module: *mut v8_local_module, - pub(crate) isolate_scope: &'isolate_scope V8IsolateScope<'isolate>, -} - -pub struct V8PersistedModule { - pub(crate) inner_persisted_module: *mut v8_persisted_module, -} - -pub(crate) extern "C" fn load_module< - T: for<'isolate, 'isolate_scope, 'c> Fn( - &'isolate V8IsolateScope<'c>, - &'isolate V8ContextScope<'isolate_scope, 'c>, - &'isolate V8LocalString<'isolate_scope, 'c>, - i64, - ) -> Option>, ->( - v8_ctx_ref: *mut v8_context_ref, - name: *mut v8_local_string, - identity_hash: c_int, -) -> *mut v8_local_module { - let isolate = V8Isolate { - inner_isolate: unsafe { v8_ContextRefGetIsolate(v8_ctx_ref) }, - no_release: true, - }; - let isolate_scope = V8IsolateScope::new(&isolate); - let ctx_scope = V8ContextScope { - inner_ctx_ref: v8_ctx_ref, - exit_on_drop: false, - isolate_scope: &isolate_scope, - }; - let name_obj = V8LocalString { - inner_string: name, - isolate_scope: &isolate_scope, - }; - let load_callback: &T = ctx_scope.get_private_data_mut_raw(RawIndex(0)).unwrap(); - let res = load_callback(&isolate_scope, &ctx_scope, &name_obj, identity_hash as i64); - match res { - Some(mut r) => { - let inner_module = r.inner_module; - r.inner_module = ptr::null_mut(); - inner_module - } - None => ptr::null_mut(), - } -} - -impl<'isolate_scope, 'isolate> V8LocalModule<'isolate_scope, 'isolate> { - pub fn initialize< - T: for<'c, 'd, 'e> Fn( - &'c V8IsolateScope<'e>, - &'c V8ContextScope<'d, 'e>, - &'c V8LocalString<'d, 'e>, - i64, - ) -> Option>, - >( - &self, - ctx_scope: &V8ContextScope, - load_module_callback: T, - ) -> bool { - ctx_scope.set_private_data_raw(RawIndex(0), &load_module_callback); - let res = unsafe { - v8_InitiateModule( - self.inner_module, - ctx_scope.inner_ctx_ref, - Some(load_module::), - ) - }; - ctx_scope.reset_private_data_raw(RawIndex(0)); - res != 0 - } - - pub fn evaluate( - &self, - ctx_scope: &V8ContextScope, - ) -> Option> { - let res = unsafe { v8_EvaluateModule(self.inner_module, ctx_scope.inner_ctx_ref) }; - if res.is_null() { - None - } else { - Some(V8LocalValue { - inner_val: res, - isolate_scope: self.isolate_scope, - }) - } - } - - /// Convert the module into a generic JS value - #[must_use] - pub fn persist(&self, isolate: &V8Isolate) -> V8PersistedModule { - let inner_persisted_module = - unsafe { v8_ModulePersist(isolate.inner_isolate, self.inner_module) }; - V8PersistedModule { - inner_persisted_module, - } - } - - pub fn get_identity_hash(&self) -> i64 { - unsafe { v8_ModuleGetIdentityHash(self.inner_module) as i64 } - } -} - -impl V8PersistedModule { - pub fn to_local<'isolate_scope, 'isolate>( - &self, - isolate_scope: &'isolate_scope V8IsolateScope<'isolate>, - ) -> V8LocalModule<'isolate_scope, 'isolate> { - let inner_module = unsafe { - v8_ModuleToLocal( - isolate_scope.isolate.inner_isolate, - self.inner_persisted_module, - ) - }; - V8LocalModule { - inner_module, - isolate_scope, - } - } -} - -impl<'isolate_scope, 'isolate> Drop for V8LocalModule<'isolate_scope, 'isolate> { - fn drop(&mut self) { - if !self.inner_module.is_null() { - unsafe { v8_FreeModule(self.inner_module) } - } - } -} - -impl Drop for V8PersistedModule { - fn drop(&mut self) { - unsafe { v8_FreePersistedModule(self.inner_persisted_module) } - } -} diff --git a/src/v8/v8_native_function.rs b/src/v8/v8_native_function.rs deleted file mode 100644 index d1598e1..0000000 --- a/src/v8/v8_native_function.rs +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright Redis Ltd. 2022 - present - * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or - * the Server Side Public License v1 (SSPLv1). - */ - -use crate::v8_c_raw::bindings::{ - v8_FreeNativeFunction, v8_NativeFunctionToValue, v8_local_native_function, -}; - -use crate::v8::isolate_scope::V8IsolateScope; -use crate::v8::v8_value::V8LocalValue; - -/// Native function object -pub struct V8LocalNativeFunction<'isolate_scope, 'isolate> { - pub(crate) inner_func: *mut v8_local_native_function, - pub(crate) isolate_scope: &'isolate_scope V8IsolateScope<'isolate>, -} - -impl<'isolate_scope, 'isolate> V8LocalNativeFunction<'isolate_scope, 'isolate> { - /// Convert the native function into a JS generic value - #[must_use] - pub fn to_value(&self) -> V8LocalValue<'isolate_scope, 'isolate> { - let inner_val = unsafe { v8_NativeFunctionToValue(self.inner_func) }; - V8LocalValue { - inner_val, - isolate_scope: self.isolate_scope, - } - } -} - -impl<'isolate_scope, 'isolate> Drop for V8LocalNativeFunction<'isolate_scope, 'isolate> { - fn drop(&mut self) { - unsafe { v8_FreeNativeFunction(self.inner_func) } - } -} diff --git a/src/v8/v8_native_function_template.rs b/src/v8/v8_native_function_template.rs deleted file mode 100644 index da348e3..0000000 --- a/src/v8/v8_native_function_template.rs +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright Redis Ltd. 2022 - present - * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or - * the Server Side Public License v1 (SSPLv1). - */ - -use crate::v8_c_raw::bindings::{ - v8_ArgsGet, v8_ArgsGetSelf, v8_FreeNativeFunctionTemplate, v8_GetCurrentCtxRef, - v8_GetCurrentIsolate, v8_NativeFunctionTemplateToFunction, v8_local_native_function_template, - v8_local_value, v8_local_value_arr, -}; - -use std::os::raw::c_void; -use std::ptr; - -use crate::v8::isolate::V8Isolate; -use crate::v8::isolate_scope::V8IsolateScope; -use crate::v8::v8_context_scope::V8ContextScope; -use crate::v8::v8_native_function::V8LocalNativeFunction; -use crate::v8::v8_object::V8LocalObject; -use crate::v8::v8_value::V8LocalValue; - -/// Native function template object -pub struct V8LocalNativeFunctionTemplate<'isolate_scope, 'isolate> { - pub(crate) inner_func: *mut v8_local_native_function_template, - pub(crate) isolate_scope: &'isolate_scope V8IsolateScope<'isolate>, -} - -/// Native function args -pub struct V8LocalNativeFunctionArgs<'isolate_scope, 'isolate> { - pub(crate) inner_arr: *mut v8_local_value_arr, - len: usize, - isolate_scope: &'isolate_scope V8IsolateScope<'isolate>, -} - -pub(crate) extern "C" fn free_pd< - T: for<'d, 'c> Fn( - &V8LocalNativeFunctionArgs<'d, 'c>, - &'d V8IsolateScope<'c>, - &V8ContextScope<'d, 'c>, - ) -> Option>, ->( - pd: *mut c_void, -) { - unsafe { - let _ = Box::from_raw(pd.cast::()); - } -} - -pub(crate) extern "C" fn native_basic_function< - T: for<'d, 'c> Fn( - &V8LocalNativeFunctionArgs<'d, 'c>, - &'d V8IsolateScope<'c>, - &V8ContextScope<'d, 'c>, - ) -> Option>, ->( - args: *mut v8_local_value_arr, - len: usize, - pd: *mut c_void, -) -> *mut v8_local_value { - let func = unsafe { &*(pd.cast::()) }; - - let inner_isolate = unsafe { v8_GetCurrentIsolate(args) }; - let isolate = V8Isolate { - inner_isolate, - no_release: true, - }; - - let isolate_scope = V8IsolateScope::new(&isolate); - - let inner_ctx_ref = unsafe { v8_GetCurrentCtxRef(inner_isolate) }; - let ctx_scope = V8ContextScope { - inner_ctx_ref, - exit_on_drop: false, - isolate_scope: &isolate_scope, - }; - - let args = V8LocalNativeFunctionArgs { - inner_arr: args, - len, - isolate_scope: &isolate_scope, - }; - - let res = func(&args, &isolate_scope, &ctx_scope); - - match res { - Some(mut r) => { - let inner_val = r.inner_val; - r.inner_val = ptr::null_mut(); - inner_val - } - None => ptr::null_mut(), - } -} - -impl<'isolate_scope, 'isolate> V8LocalNativeFunctionTemplate<'isolate_scope, 'isolate> { - pub fn to_function( - &self, - ctx_scope: &V8ContextScope, - ) -> V8LocalNativeFunction<'isolate_scope, 'isolate> { - let inner_func = unsafe { - v8_NativeFunctionTemplateToFunction(ctx_scope.inner_ctx_ref, self.inner_func) - }; - V8LocalNativeFunction { - inner_func, - isolate_scope: self.isolate_scope, - } - } -} - -impl<'isolate_scope, 'isolate> V8LocalNativeFunctionArgs<'isolate_scope, 'isolate> { - /// Return the i-th argument from the native function args - /// # Panics - #[must_use] - pub fn get(&self, i: usize) -> V8LocalValue<'isolate_scope, 'isolate> { - assert!(i <= self.len); - let val = unsafe { v8_ArgsGet(self.inner_arr, i) }; - V8LocalValue { - inner_val: val, - isolate_scope: self.isolate_scope, - } - } - - /// Return the amount of arguments passed to the native function - #[must_use] - pub const fn len(&self) -> usize { - self.len - } - - /// Checks if the list of args is empty - #[must_use] - pub const fn is_empty(&self) -> bool { - self.len == 0 - } - - /// Checks if the list of args is empty - #[must_use] - pub fn get_self(&self) -> V8LocalObject<'isolate_scope, 'isolate> { - let val = unsafe { v8_ArgsGetSelf(self.inner_arr) }; - V8LocalObject { - inner_obj: val, - isolate_scope: self.isolate_scope, - } - } - - pub const fn persist(&self) {} - - pub fn iter<'a>(&'a self) -> V8LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a> { - V8LocalNativeFunctionArgsIter { - args: self, - index: 0, - } - } -} - -pub struct V8LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a> { - args: &'a V8LocalNativeFunctionArgs<'isolate_scope, 'isolate>, - index: usize, -} - -impl<'isolate_scope, 'isolate, 'a> Iterator - for V8LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a> -{ - type Item = V8LocalValue<'isolate_scope, 'isolate>; - fn next(&mut self) -> Option { - if self.index >= self.args.len() { - return None; - } - let res = self.args.get(self.index); - self.index += 1; - Some(res) - } -} - -impl<'isolate_scope, 'isolate> Drop for V8LocalNativeFunctionTemplate<'isolate_scope, 'isolate> { - fn drop(&mut self) { - unsafe { v8_FreeNativeFunctionTemplate(self.inner_func) } - } -} diff --git a/src/v8/v8_object.rs b/src/v8/v8_object.rs deleted file mode 100644 index eabc86f..0000000 --- a/src/v8/v8_object.rs +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright Redis Ltd. 2022 - present - * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or - * the Server Side Public License v1 (SSPLv1). - */ - -use crate::v8_c_raw::bindings::{ - v8_FreeObject, v8_GetInternalFieldCount, v8_ObjectFreeze, v8_ObjectGet, - v8_ObjectGetInternalField, v8_ObjectSet, v8_ObjectSetInternalField, v8_ObjectToValue, - v8_ValueGetPropertyNames, v8_local_object, -}; - -use crate::v8::isolate_scope::V8IsolateScope; -use crate::v8::v8_array::V8LocalArray; -use crate::v8::v8_context_scope::V8ContextScope; -use crate::v8::v8_native_function_template::V8LocalNativeFunctionArgs; -use crate::v8::v8_value::V8LocalValue; - -/// JS object -pub struct V8LocalObject<'isolate_scope, 'isolate> { - pub(crate) inner_obj: *mut v8_local_object, - pub(crate) isolate_scope: &'isolate_scope V8IsolateScope<'isolate>, -} - -impl<'isolate_scope, 'isolate> V8LocalObject<'isolate_scope, 'isolate> { - /// Return the value of a given key - #[must_use] - pub fn get( - &self, - ctx_scope: &V8ContextScope, - key: &V8LocalValue, - ) -> Option> { - let inner_val = - unsafe { v8_ObjectGet(ctx_scope.inner_ctx_ref, self.inner_obj, key.inner_val) }; - if inner_val.is_null() { - None - } else { - Some(V8LocalValue { - inner_val, - isolate_scope: self.isolate_scope, - }) - } - } - - /// Sugar for get that recieve the field name as &str - #[must_use] - pub fn get_str_field( - &self, - ctx_scope: &V8ContextScope, - key: &str, - ) -> Option> { - let key = self.isolate_scope.new_string(key); - self.get(ctx_scope, &key.to_value()) - } - - pub fn set(&self, ctx_scope: &V8ContextScope, key: &V8LocalValue, val: &V8LocalValue) { - unsafe { - v8_ObjectSet( - ctx_scope.inner_ctx_ref, - self.inner_obj, - key.inner_val, - val.inner_val, - ) - }; - } - - pub fn set_native_function< - T: for<'d, 'e> Fn( - &V8LocalNativeFunctionArgs<'d, 'e>, - &'d V8IsolateScope<'e>, - &V8ContextScope<'d, 'e>, - ) -> Option>, - >( - &self, - ctx_scope: &V8ContextScope, - key: &str, - func: T, - ) { - let native_function = ctx_scope.new_native_function(func).to_value(); - let name = self.isolate_scope.new_string(key).to_value(); - unsafe { - v8_ObjectSet( - ctx_scope.inner_ctx_ref, - self.inner_obj, - name.inner_val, - native_function.inner_val, - ) - }; - } - - pub fn set_internal_field(&self, index: usize, val: &V8LocalValue) { - unsafe { v8_ObjectSetInternalField(self.inner_obj, index, val.inner_val) }; - } - - #[must_use] - pub fn get_internal_field(&self, index: usize) -> V8LocalValue<'isolate_scope, 'isolate> { - let inner_val = unsafe { v8_ObjectGetInternalField(self.inner_obj, index) }; - V8LocalValue { - inner_val, - isolate_scope: self.isolate_scope, - } - } - - #[must_use] - pub fn get_internal_field_count(&self) -> usize { - unsafe { v8_GetInternalFieldCount(self.inner_obj) } - } - - /// Convert the object into a generic JS value - #[must_use] - pub fn to_value(&self) -> V8LocalValue<'isolate_scope, 'isolate> { - let inner_val = unsafe { v8_ObjectToValue(self.inner_obj) }; - V8LocalValue { - inner_val, - isolate_scope: self.isolate_scope, - } - } - - pub fn freeze(&self, ctx_scope: &V8ContextScope) { - unsafe { v8_ObjectFreeze(ctx_scope.inner_ctx_ref, self.inner_obj) }; - } - - /// Convert the object into a generic JS value - #[must_use] - pub fn get_property_names( - &self, - ctx_scope: &V8ContextScope, - ) -> V8LocalArray<'isolate_scope, 'isolate> { - let inner_array = - unsafe { v8_ValueGetPropertyNames(ctx_scope.inner_ctx_ref, self.inner_obj) }; - V8LocalArray { - inner_array, - isolate_scope: self.isolate_scope, - } - } -} - -impl<'isolate_scope, 'isolate> Drop for V8LocalObject<'isolate_scope, 'isolate> { - fn drop(&mut self) { - unsafe { v8_FreeObject(self.inner_obj) } - } -} - -impl<'isolate_scope, 'isolate> TryFrom> - for V8LocalObject<'isolate_scope, 'isolate> -{ - type Error = &'static str; - - fn try_from(val: V8LocalValue<'isolate_scope, 'isolate>) -> Result { - if !val.is_object() { - return Err("Value is not an object"); - } - - Ok(val.as_object()) - } -} diff --git a/src/v8/v8_object_template.rs b/src/v8/v8_object_template.rs deleted file mode 100644 index 3594699..0000000 --- a/src/v8/v8_object_template.rs +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright Redis Ltd. 2022 - present - * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or - * the Server Side Public License v1 (SSPLv1). - */ - -use crate::v8_c_raw::bindings::{ - v8_FreeObjectTemplate, v8_FreePersistedObjectTemplate, v8_ObjectTemplateNewInstance, - v8_ObjectTemplatePersist, v8_ObjectTemplateSetFunction, v8_ObjectTemplateSetInternalFieldCount, - v8_ObjectTemplateSetObject, v8_ObjectTemplateSetValue, v8_PersistedObjectTemplateToLocal, - v8_local_object_template, v8_persisted_object_template, -}; - -use crate::v8::isolate_scope::V8IsolateScope; -use crate::v8::v8_context_scope::V8ContextScope; -use crate::v8::v8_native_function_template::{ - V8LocalNativeFunctionArgs, V8LocalNativeFunctionTemplate, -}; -use crate::v8::v8_object::V8LocalObject; -use crate::v8::v8_string::V8LocalString; -use crate::v8::v8_value::V8LocalValue; - -/// JS object template -pub struct V8LocalObjectTemplate<'isolate_scope, 'isolate> { - pub(crate) inner_obj: *mut v8_local_object_template, - pub(crate) isolate_scope: &'isolate_scope V8IsolateScope<'isolate>, -} - -impl<'isolate_scope, 'isolate> V8LocalObjectTemplate<'isolate_scope, 'isolate> { - /// Set a native function to the object template as a given key - pub fn set_native_function( - &mut self, - name: &V8LocalString, - func: &V8LocalNativeFunctionTemplate, - ) { - unsafe { v8_ObjectTemplateSetFunction(self.inner_obj, name.inner_string, func.inner_func) }; - } - - /// Same as `set_native_function` but gets the key as &str and the native function as closure. - pub fn add_native_function< - T: for<'d, 'e> Fn( - &V8LocalNativeFunctionArgs<'d, 'e>, - &'d V8IsolateScope<'e>, - &V8ContextScope<'d, 'e>, - ) -> Option>, - >( - &mut self, - name: &str, - func: T, - ) { - let native_func = self.isolate_scope.new_native_function_template(func); - let func_name = self.isolate_scope.new_string(name); - self.set_native_function(&func_name, &native_func); - } - - /// Set the given object to the object template on a given key - pub fn set_object(&mut self, name: &V8LocalString, obj: &Self) { - unsafe { v8_ObjectTemplateSetObject(self.inner_obj, name.inner_string, obj.inner_obj) }; - } - - pub fn set_internal_field_count(&mut self, count: usize) { - unsafe { v8_ObjectTemplateSetInternalFieldCount(self.inner_obj, count) }; - } - - /// Same as `set_object` but gets the key as &str - pub fn add_object(&mut self, name: &str, obj: &Self) { - let obj_name = self.isolate_scope.new_string(name); - self.set_object(&obj_name, obj); - } - - /// Set a generic JS value into the object template as a given key - pub fn set_value(&mut self, name: &V8LocalString, obj: &V8LocalValue) { - unsafe { v8_ObjectTemplateSetValue(self.inner_obj, name.inner_string, obj.inner_val) }; - } - - /// Same as `set_value` but gets the key as &str - pub fn add_value(&mut self, name: &str, obj: &V8LocalValue) { - let val_name = self.isolate_scope.new_string(name); - self.set_value(&val_name, obj); - } - - /// Convert the object template into a generic JS value - #[must_use] - pub fn new_instance( - &self, - ctx_scope: &V8ContextScope, - ) -> V8LocalObject<'isolate_scope, 'isolate> { - let inner_obj = - unsafe { v8_ObjectTemplateNewInstance(ctx_scope.inner_ctx_ref, self.inner_obj) }; - V8LocalObject { - inner_obj, - isolate_scope: self.isolate_scope, - } - } - - pub fn persist(&self) -> V8PersistedObjectTemplate { - let inner_persist = unsafe { - v8_ObjectTemplatePersist(self.isolate_scope.isolate.inner_isolate, self.inner_obj) - }; - V8PersistedObjectTemplate { - inner_persisted_obj_template: inner_persist, - } - } -} - -impl<'isolate_scope, 'isolate> Drop for V8LocalObjectTemplate<'isolate_scope, 'isolate> { - fn drop(&mut self) { - unsafe { v8_FreeObjectTemplate(self.inner_obj) } - } -} - -pub struct V8PersistedObjectTemplate { - pub(crate) inner_persisted_obj_template: *mut v8_persisted_object_template, -} - -impl V8PersistedObjectTemplate { - pub fn to_local<'isolate_scope, 'isolate>( - &self, - isolate_scope: &'isolate_scope V8IsolateScope<'isolate>, - ) -> V8LocalObjectTemplate<'isolate_scope, 'isolate> { - let inner_obj = unsafe { - v8_PersistedObjectTemplateToLocal( - isolate_scope.isolate.inner_isolate, - self.inner_persisted_obj_template, - ) - }; - V8LocalObjectTemplate { - inner_obj, - isolate_scope, - } - } -} - -unsafe impl Sync for V8PersistedObjectTemplate {} -unsafe impl Send for V8PersistedObjectTemplate {} - -impl Drop for V8PersistedObjectTemplate { - fn drop(&mut self) { - unsafe { v8_FreePersistedObjectTemplate(self.inner_persisted_obj_template) } - } -} diff --git a/src/v8/v8_promise.rs b/src/v8/v8_promise.rs deleted file mode 100644 index c19306a..0000000 --- a/src/v8/v8_promise.rs +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright Redis Ltd. 2022 - present - * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or - * the Server Side Public License v1 (SSPLv1). - */ - -use crate::v8_c_raw::bindings::{ - v8_FreePromise, v8_PromiseGetResult, v8_PromiseGetState, - v8_PromiseState_v8_PromiseState_Fulfilled, v8_PromiseState_v8_PromiseState_Pending, - v8_PromiseState_v8_PromiseState_Rejected, v8_PromiseThen, v8_PromiseToValue, v8_local_promise, -}; - -use crate::v8::isolate_scope::V8IsolateScope; -use crate::v8::v8_context_scope::V8ContextScope; -use crate::v8::v8_native_function::V8LocalNativeFunction; -use crate::v8::v8_value::V8LocalValue; - -pub struct V8LocalPromise<'isolate_scope, 'isolate> { - pub(crate) inner_promise: *mut v8_local_promise, - pub(crate) isolate_scope: &'isolate_scope V8IsolateScope<'isolate>, -} - -#[derive(Debug, PartialEq)] -pub enum V8PromiseState { - Fulfilled, - Rejected, - Pending, - Unknown, -} - -impl<'isolate_scope, 'isolate> V8LocalPromise<'isolate_scope, 'isolate> { - /// Set resolve and reject callbacks - pub fn then( - &self, - ctx: &V8ContextScope, - resolve: &V8LocalNativeFunction, - reject: &V8LocalNativeFunction, - ) { - unsafe { - v8_PromiseThen( - self.inner_promise, - ctx.inner_ctx_ref, - resolve.inner_func, - reject.inner_func, - ); - }; - } - - /// Return the state on the promise object - /// # Panics - #[must_use] - pub fn state(&self) -> V8PromiseState { - let inner_state = unsafe { v8_PromiseGetState(self.inner_promise) }; - if inner_state == v8_PromiseState_v8_PromiseState_Fulfilled { - V8PromiseState::Fulfilled - } else if inner_state == v8_PromiseState_v8_PromiseState_Rejected { - V8PromiseState::Rejected - } else if inner_state == v8_PromiseState_v8_PromiseState_Pending { - V8PromiseState::Pending - } else { - panic!("bad promise state"); - } - } - - /// Return the result of the promise object. - /// Only applicable if the promise object was resolved/rejected. - #[must_use] - pub fn get_result(&self) -> V8LocalValue<'isolate_scope, 'isolate> { - let inner_val = unsafe { v8_PromiseGetResult(self.inner_promise) }; - V8LocalValue { - inner_val, - isolate_scope: self.isolate_scope, - } - } - - /// Convert the promise object into a generic JS value - #[must_use] - pub fn to_value(&self) -> V8LocalValue<'isolate_scope, 'isolate> { - let inner_val = unsafe { v8_PromiseToValue(self.inner_promise) }; - V8LocalValue { - inner_val, - isolate_scope: self.isolate_scope, - } - } -} - -impl<'isolate_scope, 'isolate> Drop for V8LocalPromise<'isolate_scope, 'isolate> { - fn drop(&mut self) { - unsafe { v8_FreePromise(self.inner_promise) } - } -} diff --git a/src/v8/v8_resolver.rs b/src/v8/v8_resolver.rs deleted file mode 100644 index 0e74e3e..0000000 --- a/src/v8/v8_resolver.rs +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright Redis Ltd. 2022 - present - * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or - * the Server Side Public License v1 (SSPLv1). - */ - -use crate::v8_c_raw::bindings::{ - v8_FreeResolver, v8_ResolverGetPromise, v8_ResolverReject, v8_ResolverResolve, - v8_ResolverToValue, v8_local_resolver, -}; - -use crate::v8::isolate_scope::V8IsolateScope; -use crate::v8::v8_context_scope::V8ContextScope; -use crate::v8::v8_promise::V8LocalPromise; -use crate::v8::v8_value::V8LocalValue; - -/// JS resolver object -pub struct V8LocalResolver<'isolate_scope, 'isolate> { - pub(crate) inner_resolver: *mut v8_local_resolver, - pub(crate) isolate_scope: &'isolate_scope V8IsolateScope<'isolate>, -} - -impl<'isolate_scope, 'isolate> V8LocalResolver<'isolate_scope, 'isolate> { - /// Get the promise object assosiated with this resolver. - #[must_use] - pub fn get_promise(&self) -> V8LocalPromise<'isolate_scope, 'isolate> { - let inner_promise = unsafe { v8_ResolverGetPromise(self.inner_resolver) }; - V8LocalPromise { - inner_promise, - isolate_scope: self.isolate_scope, - } - } - - /// Resolve the resolver with the given JS value. - pub fn resolve(&self, ctx_scope: &V8ContextScope, val: &V8LocalValue) { - unsafe { v8_ResolverResolve(ctx_scope.inner_ctx_ref, self.inner_resolver, val.inner_val) }; - } - - /// Reject the resolver with the given JS value. - pub fn reject(&self, ctx_scope: &V8ContextScope, val: &V8LocalValue) { - unsafe { v8_ResolverReject(ctx_scope.inner_ctx_ref, self.inner_resolver, val.inner_val) }; - } - - /// Convert the resolver into a generic JS value. - #[must_use] - pub fn to_value(&self) -> V8LocalValue<'isolate_scope, 'isolate> { - let inner_val = unsafe { v8_ResolverToValue(self.inner_resolver) }; - V8LocalValue { - inner_val, - isolate_scope: self.isolate_scope, - } - } -} - -impl<'isolate_scope, 'isolate> Drop for V8LocalResolver<'isolate_scope, 'isolate> { - fn drop(&mut self) { - unsafe { v8_FreeResolver(self.inner_resolver) } - } -} diff --git a/src/v8/v8_script.rs b/src/v8/v8_script.rs deleted file mode 100644 index 88f4199..0000000 --- a/src/v8/v8_script.rs +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright Redis Ltd. 2022 - present - * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or - * the Server Side Public License v1 (SSPLv1). - */ - -use crate::v8_c_raw::bindings::{ - v8_FreePersistedScript, v8_FreeScript, v8_PersistedScriptToLocal, v8_Run, v8_ScriptPersist, - v8_local_script, v8_persisted_script, -}; - -use crate::v8::isolate_scope::V8IsolateScope; -use crate::v8::v8_context_scope::V8ContextScope; -use crate::v8::v8_value::V8LocalValue; - -/// JS script object -pub struct V8LocalScript<'isolate_scope, 'isolate> { - pub(crate) inner_script: *mut v8_local_script, - pub(crate) isolate_scope: &'isolate_scope V8IsolateScope<'isolate>, -} - -pub struct V8PersistedScript { - pub(crate) inner_persisted_script: *mut v8_persisted_script, -} - -impl<'isolate_scope, 'isolate> V8LocalScript<'isolate_scope, 'isolate> { - /// Run the script - #[must_use] - pub fn run(&self, ctx: &V8ContextScope) -> Option> { - let inner_val = unsafe { v8_Run(ctx.inner_ctx_ref, self.inner_script) }; - if inner_val.is_null() { - None - } else { - Some(V8LocalValue { - inner_val, - isolate_scope: self.isolate_scope, - }) - } - } - - pub fn persist(&self) -> V8PersistedScript { - let inner_persisted_script = unsafe { - v8_ScriptPersist(self.isolate_scope.isolate.inner_isolate, self.inner_script) - }; - V8PersistedScript { - inner_persisted_script, - } - } -} - -impl V8PersistedScript { - pub fn to_local<'isolate_scope, 'isolate>( - &self, - isolate_scope: &'isolate_scope V8IsolateScope<'isolate>, - ) -> V8LocalScript<'isolate_scope, 'isolate> { - let inner_script = unsafe { - v8_PersistedScriptToLocal( - isolate_scope.isolate.inner_isolate, - self.inner_persisted_script, - ) - }; - V8LocalScript { - inner_script, - isolate_scope, - } - } -} - -impl<'isolate_scope, 'isolate> Drop for V8LocalScript<'isolate_scope, 'isolate> { - fn drop(&mut self) { - unsafe { v8_FreeScript(self.inner_script) } - } -} - -unsafe impl Sync for V8PersistedScript {} -unsafe impl Send for V8PersistedScript {} - -impl Drop for V8PersistedScript { - fn drop(&mut self) { - unsafe { v8_FreePersistedScript(self.inner_persisted_script) } - } -} diff --git a/src/v8/v8_set.rs b/src/v8/v8_set.rs deleted file mode 100644 index 73b531a..0000000 --- a/src/v8/v8_set.rs +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Redis Ltd. 2022 - present - * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or - * the Server Side Public License v1 (SSPLv1). - */ - -use crate::v8_c_raw::bindings::{v8_FreeSet, v8_SetAdd, v8_SetToValue, v8_local_set}; - -use crate::v8::isolate_scope::V8IsolateScope; -use crate::v8::v8_context_scope::V8ContextScope; -use crate::v8::v8_value::V8LocalValue; - -/// JS object -pub struct V8LocalSet<'isolate_scope, 'isolate> { - pub(crate) inner_set: *mut v8_local_set, - pub(crate) isolate_scope: &'isolate_scope V8IsolateScope<'isolate>, -} - -impl<'isolate_scope, 'isolate> V8LocalSet<'isolate_scope, 'isolate> { - /// Convert the object into a generic JS value - #[must_use] - pub fn to_value(&self) -> V8LocalValue<'isolate_scope, 'isolate> { - let inner_val = unsafe { v8_SetToValue(self.inner_set) }; - V8LocalValue { - inner_val, - isolate_scope: self.isolate_scope, - } - } - - pub fn add(&self, ctx_scope: &V8ContextScope, val: &V8LocalValue) { - unsafe { v8_SetAdd(ctx_scope.inner_ctx_ref, self.inner_set, val.inner_val) }; - } -} - -impl<'isolate_scope, 'isolate> Drop for V8LocalSet<'isolate_scope, 'isolate> { - fn drop(&mut self) { - unsafe { v8_FreeSet(self.inner_set) } - } -} - -impl<'isolate_scope, 'isolate> TryFrom> - for V8LocalSet<'isolate_scope, 'isolate> -{ - type Error = &'static str; - - fn try_from(val: V8LocalValue<'isolate_scope, 'isolate>) -> Result { - if !val.is_set() { - return Err("Value is not a set"); - } - - Ok(val.as_set()) - } -} diff --git a/src/v8/v8_string.rs b/src/v8/v8_string.rs deleted file mode 100644 index 902e75d..0000000 --- a/src/v8/v8_string.rs +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Redis Ltd. 2022 - present - * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or - * the Server Side Public License v1 (SSPLv1). - */ - -use crate::v8_c_raw::bindings::{ - v8_FreeString, v8_StringToStringObject, v8_StringToValue, v8_local_string, -}; - -use crate::v8::isolate_scope::V8IsolateScope; -use crate::v8::v8_object::V8LocalObject; -use crate::v8::v8_value::V8LocalValue; - -/// JS string object -pub struct V8LocalString<'isolate_scope, 'isolate> { - pub(crate) inner_string: *mut v8_local_string, - pub(crate) isolate_scope: &'isolate_scope V8IsolateScope<'isolate>, -} - -impl<'isolate_scope, 'isolate> V8LocalString<'isolate_scope, 'isolate> { - /// Convert the string object into a generic JS object. - #[must_use] - pub fn to_value(&self) -> V8LocalValue<'isolate_scope, 'isolate> { - let inner_val = unsafe { v8_StringToValue(self.inner_string) }; - V8LocalValue { - inner_val, - isolate_scope: self.isolate_scope, - } - } - - /// Same as writing 'new String(...)'. - #[must_use] - pub fn to_string_object(&self) -> V8LocalObject<'isolate_scope, 'isolate> { - let inner_obj = unsafe { - v8_StringToStringObject(self.isolate_scope.isolate.inner_isolate, self.inner_string) - }; - V8LocalObject { - inner_obj, - isolate_scope: self.isolate_scope, - } - } -} - -impl<'isolate_scope, 'isolate> Drop for V8LocalString<'isolate_scope, 'isolate> { - fn drop(&mut self) { - unsafe { v8_FreeString(self.inner_string) } - } -} diff --git a/src/v8/v8_unlocker.rs b/src/v8/v8_unlocker.rs deleted file mode 100644 index fb4af9c..0000000 --- a/src/v8/v8_unlocker.rs +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Redis Ltd. 2022 - present - * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or - * the Server Side Public License v1 (SSPLv1). - */ - -use crate::v8::isolate_scope::V8IsolateScope; -use crate::v8_c_raw::bindings::{v8_FreeUnlocker, v8_unlocker}; - -pub struct V8Unlocker<'isolate_scope, 'isolate> { - pub(crate) inner_unlocker: *mut v8_unlocker, - pub(crate) _isolate_scope: &'isolate_scope V8IsolateScope<'isolate>, -} - -impl<'isolate_scope, 'isolate> Drop for V8Unlocker<'isolate_scope, 'isolate> { - fn drop(&mut self) { - unsafe { v8_FreeUnlocker(self.inner_unlocker) }; - } -} diff --git a/src/v8/v8_utf8.rs b/src/v8/v8_utf8.rs deleted file mode 100644 index 07d79b3..0000000 --- a/src/v8/v8_utf8.rs +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright Redis Ltd. 2022 - present - * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or - * the Server Side Public License v1 (SSPLv1). - */ - -use crate::v8::isolate_scope::V8IsolateScope; -use crate::v8::v8_value::V8LocalValue; -use crate::v8_c_raw::bindings::{v8_FreeUtf8, v8_Utf8PtrLen, v8_utf8_value}; - -use std::slice; -use std::str; - -/// JS utf8 object -pub struct V8LocalUtf8<'isolate_scope, 'isolate> { - pub(crate) inner_val: *mut v8_utf8_value, - pub(crate) _isolate_scope: &'isolate_scope V8IsolateScope<'isolate>, -} - -impl<'isolate_scope, 'isolate> V8LocalUtf8<'isolate_scope, 'isolate> { - /// Get &str from the utf8 object - /// # Panics - #[must_use] - pub fn as_str(&self) -> &str { - let mut len: usize = 0; - let buff = unsafe { v8_Utf8PtrLen(self.inner_val, &mut len) }; - let bytes = unsafe { slice::from_raw_parts(buff.cast::(), len) }; - str::from_utf8(bytes).unwrap() - } -} - -impl<'isolate_scope, 'isolate> Drop for V8LocalUtf8<'isolate_scope, 'isolate> { - fn drop(&mut self) { - unsafe { v8_FreeUtf8(self.inner_val) } - } -} - -impl<'isolate_scope, 'isolate> TryFrom> - for V8LocalUtf8<'isolate_scope, 'isolate> -{ - type Error = &'static str; - - fn try_from(val: V8LocalValue<'isolate_scope, 'isolate>) -> Result { - if !val.is_string() && !val.is_string_object() { - return Err("Value is not string"); - } - - val.to_utf8().ok_or("Failed converting to utf8") - } -} diff --git a/src/v8/v8_value.rs b/src/v8/v8_value.rs deleted file mode 100644 index 455da86..0000000 --- a/src/v8/v8_value.rs +++ /dev/null @@ -1,486 +0,0 @@ -/* - * Copyright Redis Ltd. 2022 - present - * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or - * the Server Side Public License v1 (SSPLv1). - */ - -use crate::v8_c_raw::bindings::{ - v8_FreePersistedValue, v8_FreeValue, v8_FunctionCall, v8_GetBigInt, v8_GetBool, v8_GetNumber, - v8_PersistValue, v8_PersistedValueToLocal, v8_ToUtf8, v8_ValueAsArray, v8_ValueAsArrayBuffer, - v8_ValueAsExternalData, v8_ValueAsObject, v8_ValueAsPromise, v8_ValueAsResolver, v8_ValueAsSet, - v8_ValueAsString, v8_ValueIsArray, v8_ValueIsArrayBuffer, v8_ValueIsAsyncFunction, - v8_ValueIsBigInt, v8_ValueIsBool, v8_ValueIsExternalData, v8_ValueIsFunction, v8_ValueIsNull, - v8_ValueIsNumber, v8_ValueIsObject, v8_ValueIsPromise, v8_ValueIsSet, v8_ValueIsString, - v8_ValueIsStringObject, v8_local_value, v8_persisted_value, -}; - -use std::ptr; - -use crate::v8::isolate_scope::V8IsolateScope; -use crate::v8::v8_array::V8LocalArray; -use crate::v8::v8_array_buffer::V8LocalArrayBuffer; -use crate::v8::v8_context_scope::V8ContextScope; -use crate::v8::v8_external_data::V8LocalExternalData; -use crate::v8::v8_native_function_template::V8LocalNativeFunctionArgsIter; -use crate::v8::v8_object::V8LocalObject; -use crate::v8::v8_promise::V8LocalPromise; -use crate::v8::v8_resolver::V8LocalResolver; -use crate::v8::v8_set::V8LocalSet; -use crate::v8::v8_string::V8LocalString; -use crate::v8::v8_utf8::V8LocalUtf8; -use crate::v8::OptionalTryFrom; - -/// JS generic local value -pub struct V8LocalValue<'isolate_scope, 'isolate> { - pub(crate) inner_val: *mut v8_local_value, - pub(crate) isolate_scope: &'isolate_scope V8IsolateScope<'isolate>, -} - -/// JS generic persisted value -pub struct V8PersistValue { - pub(crate) inner_val: *mut v8_persisted_value, - forget: bool, -} - -impl<'isolate_scope, 'isolate> V8LocalValue<'isolate_scope, 'isolate> { - /// Return string representation of the value or None on failure - #[must_use] - pub fn to_utf8(&self) -> Option> { - let inner_val = - unsafe { v8_ToUtf8(self.isolate_scope.isolate.inner_isolate, self.inner_val) }; - if inner_val.is_null() { - None - } else { - Some(V8LocalUtf8 { - inner_val, - _isolate_scope: self.isolate_scope, - }) - } - } - - /// Return true if the value is string and false otherwise. - #[must_use] - pub fn is_string(&self) -> bool { - (unsafe { v8_ValueIsString(self.inner_val) } != 0) - } - - /// Convert the object into a string, applicable only if the value is string. - #[must_use] - pub fn as_string(&self) -> V8LocalString<'isolate_scope, 'isolate> { - let inner_str = unsafe { v8_ValueAsString(self.inner_val) }; - V8LocalString { - inner_string: inner_str, - isolate_scope: self.isolate_scope, - } - } - - /// Return true if the value is string object and false otherwise. - #[must_use] - pub fn is_string_object(&self) -> bool { - (unsafe { v8_ValueIsStringObject(self.inner_val) } != 0) - } - - /// Return true if the value is string and false otherwise. - #[must_use] - pub fn is_array(&self) -> bool { - (unsafe { v8_ValueIsArray(self.inner_val) } != 0) - } - - /// Convert the object into a string, applicable only if the value is string. - #[must_use] - pub fn as_array(&self) -> V8LocalArray<'isolate_scope, 'isolate> { - let inner_array = unsafe { v8_ValueAsArray(self.inner_val) }; - V8LocalArray { - inner_array, - isolate_scope: self.isolate_scope, - } - } - - /// Return true if the value is string and false otherwise. - #[must_use] - pub fn is_array_buffer(&self) -> bool { - (unsafe { v8_ValueIsArrayBuffer(self.inner_val) } != 0) - } - - /// Convert the object into a string, applicable only if the value is string. - #[must_use] - pub fn as_array_buffer(&self) -> V8LocalArrayBuffer<'isolate_scope, 'isolate> { - let inner_array_buffer = unsafe { v8_ValueAsArrayBuffer(self.inner_val) }; - V8LocalArrayBuffer { - inner_array_buffer, - isolate_scope: self.isolate_scope, - } - } - - /// Return true if the value is null and false otherwise. - #[must_use] - pub fn is_null(&self) -> bool { - (unsafe { v8_ValueIsNull(self.inner_val) } != 0) - } - - /// Return true if the value is function and false otherwise. - #[must_use] - pub fn is_function(&self) -> bool { - (unsafe { v8_ValueIsFunction(self.inner_val) } != 0) - } - - /// Return true if the value is async function and false otherwise. - #[must_use] - pub fn is_async_function(&self) -> bool { - (unsafe { v8_ValueIsAsyncFunction(self.inner_val) } != 0) - } - - /// Return true if the value is number and false otherwise. - #[must_use] - pub fn is_number(&self) -> bool { - (unsafe { v8_ValueIsNumber(self.inner_val) } != 0) - } - - pub fn get_number(&self) -> f64 { - unsafe { v8_GetNumber(self.inner_val) } - } - - /// Return true if the value is number and false otherwise. - #[must_use] - pub fn is_long(&self) -> bool { - (unsafe { v8_ValueIsBigInt(self.inner_val) } != 0) - } - - pub fn get_long(&self) -> i64 { - unsafe { v8_GetBigInt(self.inner_val) } - } - - /// Return true if the value is boolean and false otherwise. - #[must_use] - pub fn is_boolean(&self) -> bool { - (unsafe { v8_ValueIsBool(self.inner_val) } != 0) - } - - pub fn get_boolean(&self) -> bool { - (unsafe { v8_GetBool(self.inner_val) } != 0) - } - - /// Return true if the value is promise and false otherwise. - #[must_use] - pub fn is_promise(&self) -> bool { - (unsafe { v8_ValueIsPromise(self.inner_val) } != 0) - } - - /// Convert the object into a promise, applicable only if the object is promise. - #[must_use] - pub fn as_promise(&self) -> V8LocalPromise<'isolate_scope, 'isolate> { - let inner_promise = unsafe { v8_ValueAsPromise(self.inner_val) }; - V8LocalPromise { - inner_promise, - isolate_scope: self.isolate_scope, - } - } - - /// Convert the object into a resolver, applicable only if the object is resolver. - #[must_use] - pub fn as_resolver(&self) -> V8LocalResolver<'isolate_scope, 'isolate> { - let inner_resolver = unsafe { v8_ValueAsResolver(self.inner_val) }; - V8LocalResolver { - inner_resolver, - isolate_scope: self.isolate_scope, - } - } - - /// Return true if the value is object and false otherwise. - #[must_use] - pub fn is_object(&self) -> bool { - (unsafe { v8_ValueIsObject(self.inner_val) } != 0) - } - - /// Convert the object into a promise, applicable only if the object is promise. - #[must_use] - pub fn as_object(&self) -> V8LocalObject<'isolate_scope, 'isolate> { - let inner_obj = unsafe { v8_ValueAsObject(self.inner_val) }; - V8LocalObject { - inner_obj, - isolate_scope: self.isolate_scope, - } - } - - #[must_use] - pub fn is_external(&self) -> bool { - (unsafe { v8_ValueIsExternalData(self.inner_val) } != 0) - } - - #[must_use] - pub fn as_external_data(&self) -> V8LocalExternalData<'isolate_scope, 'isolate> { - let inner_obj = unsafe { v8_ValueAsExternalData(self.inner_val) }; - V8LocalExternalData { - inner_ext: inner_obj, - isolate_scope: self.isolate_scope, - } - } - - /// Return true if the value is set and false otherwise. - #[must_use] - pub fn is_set(&self) -> bool { - (unsafe { v8_ValueIsSet(self.inner_val) } != 0) - } - - /// Convert the object into a promise, applicable only if the object is promise. - #[must_use] - pub fn as_set(&self) -> V8LocalSet<'isolate_scope, 'isolate> { - let inner_set = unsafe { v8_ValueAsSet(self.inner_val) }; - V8LocalSet { - inner_set, - isolate_scope: self.isolate_scope, - } - } - - /// Persist the local object so it can be saved beyond the current handlers scope. - #[must_use] - pub fn persist(&self) -> V8PersistValue { - let inner_val = - unsafe { v8_PersistValue(self.isolate_scope.isolate.inner_isolate, self.inner_val) }; - V8PersistValue { - inner_val, - forget: false, - } - } - - /// Run the value, applicable only if the value is a function or async function. - #[must_use] - pub fn call(&self, ctx: &V8ContextScope, args: Option<&[&Self]>) -> Option { - let res = match args { - Some(args) => { - let args = args - .iter() - .map(|v| v.inner_val) - .collect::>(); - let ptr = args.as_ptr(); - unsafe { v8_FunctionCall(ctx.inner_ctx_ref, self.inner_val, args.len(), ptr) } - } - None => unsafe { v8_FunctionCall(ctx.inner_ctx_ref, self.inner_val, 0, ptr::null()) }, - }; - - if res.is_null() { - None - } else { - Some(Self { - inner_val: res, - isolate_scope: self.isolate_scope, - }) - } - } -} - -impl V8PersistValue { - /// Convert the persisted value back to local value. - #[must_use] - pub fn as_local<'isolate, 'isolate_scope>( - &self, - isolate_scope: &'isolate_scope V8IsolateScope<'isolate>, - ) -> V8LocalValue<'isolate_scope, 'isolate> { - assert!(!self.inner_val.is_null()); - let inner_val = unsafe { - v8_PersistedValueToLocal(isolate_scope.isolate.inner_isolate, self.inner_val) - }; - V8LocalValue { - inner_val, - isolate_scope, - } - } - - pub fn forget(&mut self) { - assert!(!self.inner_val.is_null()); - self.forget = true; - } - - pub fn take_local<'isolate, 'isolate_scope>( - &mut self, - isolate_scope: &'isolate_scope V8IsolateScope<'isolate>, - ) -> V8LocalValue<'isolate_scope, 'isolate> { - let val = self.as_local(isolate_scope); - unsafe { v8_FreePersistedValue(self.inner_val) } - self.forget(); - self.inner_val = ptr::null_mut(); - val - } -} - -unsafe impl Sync for V8PersistValue {} -unsafe impl Send for V8PersistValue {} - -impl<'isolate_scope, 'isolate> Drop for V8LocalValue<'isolate_scope, 'isolate> { - fn drop(&mut self) { - if !self.inner_val.is_null() { - unsafe { v8_FreeValue(self.inner_val) } - } - } -} - -impl Drop for V8PersistValue { - fn drop(&mut self) { - if self.forget { - return; - } - unsafe { v8_FreePersistedValue(self.inner_val) } - } -} - -impl<'isolate_scope, 'isolate> TryFrom> for i64 { - type Error = &'static str; - - fn try_from(val: V8LocalValue<'isolate_scope, 'isolate>) -> Result { - if !val.is_long() { - return Err("Value is not long"); - } - - Ok(val.get_long()) - } -} - -impl<'isolate_scope, 'isolate> TryFrom> for f64 { - type Error = &'static str; - - fn try_from(val: V8LocalValue<'isolate_scope, 'isolate>) -> Result { - if !val.is_number() { - return Err("Value is not number"); - } - - Ok(val.get_number()) - } -} - -impl<'isolate_scope, 'isolate> TryFrom> for String { - type Error = &'static str; - - fn try_from(val: V8LocalValue<'isolate_scope, 'isolate>) -> Result { - if !val.is_string() && !val.is_string_object() { - return Err("Value is not string"); - } - - let v8_utf8 = match val.to_utf8() { - Some(val) => val, - None => return Err("Failed converting to utf8"), - }; - Ok(v8_utf8.as_str().to_string()) - } -} - -impl<'isolate_scope, 'isolate> TryFrom> for bool { - type Error = &'static str; - - fn try_from(val: V8LocalValue<'isolate_scope, 'isolate>) -> Result { - if !val.is_boolean() { - return Err("Value is not a boolean"); - } - - Ok(val.get_boolean()) - } -} - -// impl<'isolate_scope, 'isolate> TryFrom> for V8LocalValue<'isolate_scope, 'isolate> -// { -// type Error = &'static str; - -// fn try_from(val: V8LocalValue<'isolate_scope, 'isolate>) -> Result { -// Ok(val) -// } -// } - -macro_rules! from_iter_impl { - ( $x:ty ) => { - impl<'isolate_scope, 'isolate, 'a> - TryFrom<&mut V8LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a>> for $x - { - type Error = &'static str; - fn try_from( - val: &mut V8LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a>, - ) -> Result { - match val.next() { - Some(val) => val.try_into(), - None => Err("Wrong number of arguments given".into()), - } - } - } - }; -} - -from_iter_impl!(i64); -from_iter_impl!(f64); -from_iter_impl!(String); -from_iter_impl!(bool); -from_iter_impl!(V8LocalArray<'isolate_scope, 'isolate>); -from_iter_impl!(V8LocalArrayBuffer<'isolate_scope, 'isolate>); -from_iter_impl!(V8LocalObject<'isolate_scope, 'isolate>); -from_iter_impl!(V8LocalSet<'isolate_scope, 'isolate>); -from_iter_impl!(V8LocalUtf8<'isolate_scope, 'isolate>); - -impl<'isolate_scope, 'isolate, 'a> - TryFrom<&mut V8LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a>> - for V8LocalValue<'isolate_scope, 'isolate> -{ - type Error = &'static str; - fn try_from( - val: &mut V8LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a>, - ) -> Result { - val.next().ok_or("Wrong number of arguments given") - } -} - -impl<'isolate_scope, 'isolate, 'a, T> - OptionalTryFrom<&mut V8LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a>> for T -where - T: TryFrom, Error = &'static str>, -{ - type Error = &'static str; - fn optional_try_from( - val: &mut V8LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a>, - ) -> Result, Self::Error> { - let val = match val.next() { - Some(v) => v, - None => return Ok(None), - }; - val.try_into().map(|v| Some(v)) - } -} - -impl<'isolate_scope, 'isolate, 'a> - OptionalTryFrom<&mut V8LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a>> - for V8LocalValue<'isolate_scope, 'isolate> -{ - type Error = &'static str; - fn optional_try_from( - val: &mut V8LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a>, - ) -> Result, Self::Error> { - Ok(val.next()) - } -} - -impl<'isolate_scope, 'isolate, 'a, T> - TryFrom<&mut V8LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a>> for Vec -where - T: TryFrom, Error = &'static str>, -{ - type Error = &'static str; - fn try_from( - val: &mut V8LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a>, - ) -> Result { - let mut res = Self::new(); - for v in val { - match v.try_into() { - Ok(v) => res.push(v), - Err(e) => return Err(e), - } - } - Ok(res) - } -} - -impl<'isolate_scope, 'isolate, 'a> - TryFrom<&mut V8LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a>> - for Vec> -{ - type Error = &'static str; - fn try_from( - val: &mut V8LocalNativeFunctionArgsIter<'isolate_scope, 'isolate, 'a>, - ) -> Result { - Ok(val.collect()) - } -} diff --git a/v8-rs-derive/Cargo.toml b/v8-rs-derive/Cargo.toml index 3b639b9..717b4e1 100644 --- a/v8-rs-derive/Cargo.toml +++ b/v8-rs-derive/Cargo.toml @@ -6,10 +6,10 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -syn = { version="1.0", features = ["full"]} -quote = "1.0" +syn = { version = "1", features = ["full"]} +quote = "1" [lib] name = "v8_derive" path = "src/lib.rs" -proc-macro = true \ No newline at end of file +proc-macro = true diff --git a/v8-rs-derive/src/lib.rs b/v8-rs-derive/src/lib.rs index f968862..95fb223 100644 --- a/v8-rs-derive/src/lib.rs +++ b/v8-rs-derive/src/lib.rs @@ -209,7 +209,7 @@ pub fn new_native_function(item: TokenStream) -> TokenStream { fn __create_closure__(f: F) -> F where - F: for<'i_s, 'i> Fn(&'i_s v8_rs::v8::isolate_scope::V8IsolateScope<'i>, &v8_rs::v8::v8_context_scope::V8ContextScope<'i_s, 'i>, #(#types_for_closure, )*) -> Result>, E>, + F: for<'i_s, 'i> Fn(&'i_s v8_rs::v8::isolate_scope::IsolateScope<'i>, &v8_rs::v8::context_scope::ContextScope<'i_s, 'i>, #(#types_for_closure, )*) -> Result>, E>, E: std::fmt::Display, { f