This commit is contained in:
LongYinan 2018-04-28 16:26:38 +08:00
commit c05ab719b3
No known key found for this signature in database
GPG key ID: D05299933DBE44D3
21 changed files with 1481 additions and 0 deletions

9
.editorconfig Normal file
View file

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_size = 2
indent_style = space

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
target/
*.log
.DS_Store
Cargo.lock
node_modules

13
Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "napi-rs"
version = "0.1.0"
authors = ["Nathan Sobo <nathan@github.com>", "Yinan Long <lynweklm@gmail.com>"]
license = "MIT"
[dependencies]
futures = "0.1.17"
[build-dependencies]
bindgen = "0.32.1"
cc = "1.0"
glob = "0.2.11"

58
README.md Normal file
View file

@ -0,0 +1,58 @@
# napi
> This project was initialized from [xray](https://github.com/atom/xray)
A minimal library for building compiled Node add-ons in Rust.
This library depends on N-API and requires Node 8.9 or later. It is still pretty raw and has not been tested in a production setting.
One nice feature is that this crate allows you to build add-ons purely with the Rust toolchain and without involving `node-gyp`.
## Building
This repository is a Cargo crate *and* an npm module. Any napi-based add-on should also contain *both* `Cargo.toml` to make it a Cargo crate and a `package.json` to make it an npm module.
In your `Cargo.toml` you need to set the `crate-type` to `"cdylib"` so that cargo builds a C-style shared library that can be dynamically loaded by the Node executable. You'll also want to add this crate as a dependency.
```
[lib]
crate-type = ["cdylib"]
```
Building napi-based add-ons directly with `cargo build` isn't recommended, because you'll need to provide a `NODE_INCLUDE_PATH` pointing to the `include` directory for the version of Node you're targeting, as well as some special linker flags that can't be specified in the Cargo configuration.
Instead, you'll want to use the `napi` script, which will be installed automatically at `node_modules/.bin/napi` if you include `napi` as a dependency in your add-on's `package.json`. The napi script supports the following subcommands.
* `napi build [--debug]` Runs `cargo build` with a `NODE_INCLUDE_PATH` based on the path of the Node executable used to run the script and the required linker flags. The optional `--debug` flag will build in debug mode. After building, the script renames the dynamic library to have the `.node` extension to match the convention in the Node.js ecosystem.
* `napi check` Runs `cargo check` with a `NODE_INCLUDE_PATH` based on the Node executable used to run the script.
The `napi` script will be available on the `PATH` of any scripts you define in the `scripts` section of your `package.json`, enabling a setup like this:
```json
{
"name": "my-add-on",
"version": "1.0.0",
"scripts": {
"build": "napi build",
"build-debug": "napi build --debug",
"check": "napi check"
},
"dependencies": {
"napi": "https://github.com/atom/napi"
}
}
```
So far, the `napi` build script has only been tested on macOS. See the included `test_module` for an example add-on.
## Testing
Because libraries that depend on this crate must be loaded into a Node executable in order to resolve symbols, all tests are written in JavaScript in the `test_module` subdirectory.
To run tests:
```sh
cd test_module
npm run build
npm test
```

52
build.rs Normal file
View file

@ -0,0 +1,52 @@
extern crate bindgen;
extern crate cc;
extern crate glob;
use glob::glob;
use std::env;
fn expect_env(key: &str) -> String {
let value = env::var(key);
if value.is_err() {
eprintln!("{} environment variable is not defined.", key);
eprintln!("Make sure you're running cargo via the `napi` wrapper script to assign correct environment variables and options.");
std::process::exit(1);
};
value.unwrap()
}
fn main() {
let node_include_path = expect_env("NODE_INCLUDE_PATH");
let node_major_version = expect_env("NODE_MAJOR_VERSION");
println!("cargo:rerun-if-env-changed=NODE_INCLUDE_PATH");
for entry in glob("./src/sys/**/*.*").unwrap() {
println!(
"cargo:rerun-if-changed={}",
entry.unwrap().to_str().unwrap()
);
}
// Activate the "node8" or "node9" feature for compatibility with
// different versions of Node.js/N-API.
println!("cargo:rustc-cfg=node{}", node_major_version);
bindgen::Builder::default()
.header("src/sys/bindings.h")
.clang_arg(String::from("-I") + &node_include_path)
.rustified_enum("(napi_|uv_).+")
.whitelist_function("(napi_|uv_|extras_).+")
.whitelist_type("(napi_|uv_|extras_).+")
.generate()
.expect("Unable to generate napi bindings")
.write_to_file("src/sys/bindings.rs")
.expect("Unable to write napi bindings");
cc::Build::new()
.cpp(true)
.include(&node_include_path)
.file("src/sys/bindings.cc")
.flag("-std=c++0x")
.flag("-Wno-unused-parameter")
.compile("extras");
}

32
package.json Normal file
View file

@ -0,0 +1,32 @@
{
"name": "napi-rs",
"version": "0.1.0",
"description":
"A minimal library for building compiled Node add-ons in Rust.",
"bin": {
"napi": "scripts/napi.js"
},
"scripts": {
"check": "scripts/napi.js check"
},
"repository": {
"type": "git",
"url": "git+https://github.com/atom/napi.git"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/atom/napi/issues"
},
"homepage": "https://github.com/atom/napi#readme",
"dependencies": {
"minimist": "^1.2.0"
},
"prettier": {
"printWidth": 80,
"semi": false,
"singleQuote": true,
"trailingComma": "all",
"arrowParens": "always",
"parser": "typescript"
}
}

81
scripts/napi.js Executable file
View file

@ -0,0 +1,81 @@
#!/usr/bin/env node
const parseArgs = require('minimist')
const cp = require('child_process')
const path = require('path')
const os = require('os')
const parsedNodeVersion = process.versions.node.match(/^(\d+)\.(\d+)\.(\d+)$/)
const nodeMajorVersion = parseInt(parsedNodeVersion[1])
const nodeMinorVersion = parseInt(parsedNodeVersion[2])
if (nodeMajorVersion < 8 || (nodeMajorVersion === 8 && nodeMinorVersion < 9)) {
console.error('This build script should be run on Node 8.9 or greater')
process.exit(1)
}
const argv = parseArgs(process.argv.slice(2), {
boolean: ['release'],
})
const subcommand = argv._[0] || 'build'
const nodeIncludePath = path.join(
process.argv[0],
'..',
'..',
'include',
'node',
)
const moduleName = path.basename(process.cwd()).replace('-', '_')
process.env.NODE_INCLUDE_PATH = nodeIncludePath
process.env.NODE_MAJOR_VERSION = nodeMajorVersion
const platform = os.platform()
let libExt, platformArgs
// Platform based massaging for build commands
switch (platform) {
case 'darwin':
libExt = '.dylib'
platformArgs = '-undefined dynamic_lookup -export_dynamic'
break
case 'win32':
libExt = '.dll'
platformArgs = '-undefined dynamic_lookup -export_dynamic'
break
case 'linux':
libExt = '.so'
platformArgs = '-undefined=dynamic_lookup -export_dynamic'
break
default:
console.error(
'Operating system not currently supported or recognized by the build script',
)
process.exit(1)
}
switch (subcommand) {
case 'build':
const releaseFlag = argv.release ? '--release' : ''
const targetDir = argv.release ? 'release' : 'debug'
cp.execSync(
`cargo rustc ${releaseFlag} -- -Clink-args=\"${platformArgs}\"`,
{ stdio: 'inherit' },
)
cp.execSync(`mkdir -p target/${targetDir}`)
cp.execSync(
`cp ${path.join(
process.cwd(),
'target',
targetDir,
'lib' + moduleName + libExt,
)} target/${targetDir}/${moduleName}.node`,
{ stdio: 'inherit' },
)
break
case 'check':
cp.execSync(`cargo check`, { stdio: 'inherit' })
case 'doc':
cp.execSync(`cargo doc`, { stdio: 'inherit' })
}

125
src/executor.rs Normal file
View file

@ -0,0 +1,125 @@
use super::sys;
use futures::executor::{self, Notify, Spawn};
use futures::future::{ExecuteError, Executor};
use futures::{Async, Future};
use std::mem;
use std::os::raw::c_void;
use std::ptr;
use std::sync::{Arc, RwLock};
pub struct LibuvExecutor {
event_loop: *mut sys::uv_loop_t,
}
struct Task<T: 'static + Future> {
spawn: Spawn<T>,
notify_handle: Arc<TaskNotifyHandle>,
}
struct TaskNotifyHandle(RwLock<Option<UvAsyncHandle>>);
struct UvAsyncHandle(Box<sys::uv_async_t>);
impl LibuvExecutor {
pub fn new(event_loop: *mut sys::uv_loop_t) -> Self {
Self { event_loop }
}
}
impl<F> Executor<F> for LibuvExecutor
where
F: 'static + Future<Item = (), Error = ()>,
{
fn execute(&self, future: F) -> Result<(), ExecuteError<F>> {
let spawn = executor::spawn(future);
unsafe {
let mut task = Box::new(Task {
spawn,
notify_handle: mem::uninitialized(),
});
ptr::write(
&mut task.notify_handle,
Arc::new(TaskNotifyHandle::new(
self.event_loop,
Some(poll_future_on_main_thread::<F>),
mem::transmute_copy(&task),
)),
);
if !task.poll_future() {
mem::forget(task)
}
}
Ok(())
}
}
impl<T: 'static + Future> Task<T> {
fn poll_future(&mut self) -> bool {
match self.spawn.poll_future_notify(&self.notify_handle, 0) {
Ok(Async::Ready(_)) => {
let mut handle = self.notify_handle.0.write().unwrap().take().unwrap();
handle.close();
true
}
Ok(Async::NotReady) => false,
Err(_) => panic!("Future yielded an error"),
}
}
}
impl TaskNotifyHandle {
fn new(event_loop: *mut sys::uv_loop_t, callback: sys::uv_async_cb, data: *mut c_void) -> Self {
TaskNotifyHandle(RwLock::new(Some(UvAsyncHandle::new(
event_loop, callback, data,
))))
}
}
impl Notify for TaskNotifyHandle {
fn notify(&self, _id: usize) {
if let Some(ref uv_handle) = *self.0.read().unwrap() {
unsafe {
sys::uv_async_send(mem::transmute_copy(&uv_handle.0));
}
}
}
}
impl UvAsyncHandle {
fn new(event_loop: *mut sys::uv_loop_t, callback: sys::uv_async_cb, data: *mut c_void) -> Self {
unsafe {
let mut handle = UvAsyncHandle(Box::new(mem::uninitialized()));
let status = sys::uv_async_init(event_loop, mem::transmute_copy(&handle.0), callback);
assert!(status == 0, "Non-zero status returned from uv_async_init");
handle.0.data = data;
handle
}
}
fn close(self) {
unsafe {
sys::uv_close(mem::transmute_copy(&self.0), Some(drop_handle_after_close));
mem::forget(self.0);
}
}
}
unsafe impl Send for UvAsyncHandle {}
unsafe impl Sync for UvAsyncHandle {}
extern "C" fn drop_handle_after_close(handle: *mut sys::uv_handle_t) {
unsafe {
Box::from_raw(handle);
}
}
extern "C" fn poll_future_on_main_thread<T: 'static + Future>(handle: *mut sys::uv_async_t) {
let mut task: Box<Task<T>> = unsafe { Box::from_raw((*handle).data as *mut Task<T>) };
if !task.poll_future() {
mem::forget(task); // Don't drop task if it isn't complete.
}
}

820
src/lib.rs Normal file
View file

@ -0,0 +1,820 @@
pub extern crate futures;
use std::any::TypeId;
use std::ffi::CString;
use std::marker::PhantomData;
use std::mem;
use std::os::raw::{c_char, c_void};
use std::ptr;
use std::string::String as RustString;
mod executor;
pub mod sys;
pub use sys::Status;
pub type Result<T> = std::result::Result<T, Error>;
pub type Callback = extern "C" fn(sys::napi_env, sys::napi_callback_info) -> sys::napi_value;
#[derive(Debug)]
pub struct Error {
status: Status,
}
#[derive(Clone, Copy, Debug)]
pub struct Env(sys::napi_env);
// Value types
#[derive(Clone, Copy, Debug)]
pub struct Any;
#[derive(Clone, Copy, Debug)]
pub struct Undefined;
#[derive(Clone, Copy, Debug)]
pub struct Boolean;
#[derive(Clone, Copy, Debug)]
pub struct Number;
#[derive(Clone, Copy, Debug)]
pub struct String;
#[derive(Clone, Copy, Debug)]
pub struct Object;
#[derive(Clone, Copy, Debug)]
pub struct Function;
#[derive(Clone, Copy, Debug)]
pub struct Value<'env, T> {
env: &'env Env,
raw_value: sys::napi_value,
_marker: PhantomData<T>,
}
pub struct Ref<T> {
raw_env: sys::napi_env,
raw_ref: sys::napi_ref,
_marker: PhantomData<T>,
}
pub struct AsyncContext {
raw_env: sys::napi_env,
raw_context: sys::napi_async_context,
raw_resource: sys::napi_ref,
}
pub struct Deferred(sys::napi_deferred);
#[derive(Clone, Debug)]
pub struct Property {
name: RustString,
raw_descriptor: sys::napi_property_descriptor,
}
#[repr(C)]
struct TaggedObject<T> {
type_id: TypeId,
object: Option<T>,
}
#[macro_export]
macro_rules! register_module {
($module_name:ident, $init:ident) => {
#[no_mangle]
#[cfg_attr(target_os = "linux", link_section = ".ctors")]
#[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]
#[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
pub static __REGISTER_MODULE: extern "C" fn() = {
use std::io::Write;
use std::os::raw::c_char;
use std::ptr;
use $crate::sys;
extern "C" fn register_module() {
static mut MODULE_DESCRIPTOR: Option<sys::napi_module> = None;
unsafe {
MODULE_DESCRIPTOR = Some(sys::napi_module {
nm_version: 1,
nm_flags: 0,
nm_filename: concat!(file!(), "\0").as_ptr() as *const c_char,
nm_register_func: Some(init_module),
nm_modname: concat!(stringify!($module_name), "\0").as_ptr() as *const c_char,
nm_priv: 0 as *mut _,
reserved: [0 as *mut _; 4],
});
sys::napi_module_register(MODULE_DESCRIPTOR.as_mut().unwrap() as *mut sys::napi_module);
}
extern "C" fn init_module(
raw_env: sys::napi_env,
raw_exports: sys::napi_value,
) -> sys::napi_value {
let env = Env::from_raw(raw_env);
let mut exports: Value<Object> = Value::from_raw(&env, raw_exports);
let result = $init(&env, &mut exports);
match result {
Ok(Some(exports)) => exports.into_raw(),
Ok(None) => ptr::null_mut(),
Err(e) => {
let _ = writeln!(::std::io::stderr(), "Error initializing module: {:?}", e);
ptr::null_mut()
}
}
}
}
register_module
};
};
}
#[macro_export]
macro_rules! callback {
($callback_expr:expr) => {{
use std::io::Write;
use std::mem;
use std::os::raw::c_char;
use std::ptr;
use $crate::sys;
use $crate::{Any, Env, Status, Value};
extern "C" fn raw_callback(
raw_env: sys::napi_env,
cb_info: sys::napi_callback_info,
) -> sys::napi_value {
const MAX_ARGC: usize = 8;
let mut argc = MAX_ARGC;
let mut raw_args: [$crate::sys::napi_value; MAX_ARGC] = unsafe { mem::uninitialized() };
let mut raw_this = ptr::null_mut();
unsafe {
let status = sys::napi_get_cb_info(
raw_env,
cb_info,
&mut argc,
&mut raw_args[0],
&mut raw_this,
ptr::null_mut(),
);
debug_assert!(Status::from(status) == Status::Ok);
}
let env = Env::from_raw(raw_env);
let this = Value::from_raw(&env, raw_this);
let mut args: [Value<Any>; 8] = unsafe { mem::uninitialized() };
for (i, raw_arg) in raw_args.into_iter().enumerate() {
args[i] = Value::from_raw(&env, *raw_arg)
}
let callback = $callback_expr;
let result = callback(&env, this, &args[0..argc]);
match result {
Ok(Some(result)) => result.into_raw(),
Ok(None) => env.get_undefined().into_raw(),
Err(e) => {
let _ = writeln!(::std::io::stderr(), "Error calling function: {:?}", e);
let message = format!("{:?}", e);
unsafe {
$crate::sys::napi_throw_error(raw_env, ptr::null(), message.as_ptr() as *const c_char);
}
env.get_undefined().into_raw()
}
}
}
raw_callback
}};
}
impl Error {
pub fn new(status: Status) -> Self {
Error { status: status }
}
}
impl From<std::ffi::NulError> for Error {
fn from(_error: std::ffi::NulError) -> Self {
Error {
status: Status::StringContainsNull,
}
}
}
impl Env {
pub fn from_raw(env: sys::napi_env) -> Self {
Env(env)
}
pub fn get_undefined<'a>(&'a self) -> Value<'a, Undefined> {
let mut raw_value = ptr::null_mut();
let status = unsafe { sys::napi_get_undefined(self.0, &mut raw_value) };
debug_assert!(Status::from(status) == Status::Ok);
Value::from_raw(self, raw_value)
}
pub fn get_boolean(&self, value: bool) -> Value<Boolean> {
let mut raw_value = ptr::null_mut();
let status = unsafe { sys::napi_get_boolean(self.0, value, &mut raw_value) };
debug_assert!(Status::from(status) == Status::Ok);
Value::from_raw(self, raw_value)
}
pub fn create_int64<'a>(&'a self, int: i64) -> Value<'a, Number> {
let mut raw_value = ptr::null_mut();
let status =
unsafe { sys::napi_create_int64(self.0, int, (&mut raw_value) as *mut sys::napi_value) };
debug_assert!(Status::from(status) == Status::Ok);
Value::from_raw(self, raw_value)
}
pub fn create_string<'a, 'b>(&'a self, s: &'b str) -> Value<'a, String> {
let mut raw_value = ptr::null_mut();
let status = unsafe {
sys::napi_create_string_utf8(self.0, s.as_ptr() as *const c_char, s.len(), &mut raw_value)
};
debug_assert!(Status::from(status) == Status::Ok);
Value::from_raw(self, raw_value)
}
pub fn create_string_utf16<'a, 'b>(&'a self, chars: &[u16]) -> Value<'a, String> {
let mut raw_value = ptr::null_mut();
let status =
unsafe { sys::napi_create_string_utf16(self.0, chars.as_ptr(), chars.len(), &mut raw_value) };
debug_assert!(Status::from(status) == Status::Ok);
Value::from_raw(self, raw_value)
}
pub fn create_object<'a>(&'a self) -> Value<'a, Object> {
let mut raw_value = ptr::null_mut();
let status = unsafe { sys::napi_create_object(self.0, &mut raw_value) };
debug_assert!(Status::from(status) == Status::Ok);
Value::from_raw(self, raw_value)
}
pub fn create_array_with_length(&self, length: usize) -> Value<Object> {
let mut raw_value = ptr::null_mut();
let status = unsafe { sys::napi_create_array_with_length(self.0, length, &mut raw_value) };
debug_assert!(Status::from(status) == Status::Ok);
Value::from_raw(self, raw_value)
}
pub fn create_function<'a, 'b>(
&'a self,
name: &'b str,
callback: Callback,
) -> Value<'a, Function> {
let mut raw_result = ptr::null_mut();
let status = unsafe {
sys::napi_create_function(
self.0,
name.as_ptr() as *const c_char,
name.len(),
Some(callback),
callback as *mut c_void,
&mut raw_result,
)
};
debug_assert!(Status::from(status) == Status::Ok);
Value::from_raw(self, raw_result)
}
pub fn create_reference<T>(&self, value: &Value<T>) -> Ref<T> {
let mut raw_ref = ptr::null_mut();
unsafe {
let status = sys::napi_create_reference(self.0, value.raw_value, 1, &mut raw_ref);
debug_assert!(Status::from(status) == Status::Ok);
};
Ref {
raw_env: self.0,
raw_ref,
_marker: PhantomData,
}
}
pub fn get_reference_value<T: ValueType>(&self, reference: &Ref<T>) -> Value<T> {
let mut raw_value = ptr::null_mut();
unsafe {
let status = sys::napi_get_reference_value(self.0, reference.raw_ref, &mut raw_value);
debug_assert!(Status::from(status) == Status::Ok);
};
Value::from_raw(self, raw_value)
}
pub fn define_class<'a, 'b>(
&'a self,
name: &'b str,
constructor_cb: Callback,
properties: Vec<Property>,
) -> Value<'a, Function> {
let mut raw_result = ptr::null_mut();
let raw_properties = properties
.into_iter()
.map(|prop| prop.into_raw(self))
.collect::<Vec<sys::napi_property_descriptor>>();
let status = unsafe {
sys::napi_define_class(
self.0,
name.as_ptr() as *const c_char,
name.len(),
Some(constructor_cb),
ptr::null_mut(),
raw_properties.len(),
raw_properties.as_ptr(),
&mut raw_result,
)
};
debug_assert!(Status::from(status) == Status::Ok);
Value::from_raw(self, raw_result)
}
pub fn wrap<T: 'static>(&self, js_object: &mut Value<Object>, native_object: T) -> Result<()> {
let status = unsafe {
sys::napi_wrap(
self.0,
js_object.raw_value,
Box::into_raw(Box::new(TaggedObject::new(native_object))) as *mut c_void,
Some(raw_finalize::<T>),
ptr::null_mut(),
ptr::null_mut(),
)
};
check_status(status).or(Ok(()))
}
pub fn unwrap<T: 'static>(&self, js_object: &Value<Object>) -> Result<&mut T> {
unsafe {
let mut unknown_tagged_object: *mut c_void = ptr::null_mut();
let status = sys::napi_unwrap(self.0, js_object.raw_value, &mut unknown_tagged_object);
check_status(status)?;
let type_id: *const TypeId = mem::transmute(unknown_tagged_object);
if *type_id == TypeId::of::<T>() {
let tagged_object: *mut TaggedObject<T> = mem::transmute(unknown_tagged_object);
(*tagged_object).object.as_mut().ok_or(Error {
status: Status::InvalidArg,
})
} else {
Err(Error {
status: Status::InvalidArg,
})
}
}
}
pub fn drop_wrapped<T: 'static>(&self, js_object: Value<Object>) -> Result<()> {
unsafe {
let mut unknown_tagged_object: *mut c_void = ptr::null_mut();
let status = sys::napi_unwrap(self.0, js_object.raw_value, &mut unknown_tagged_object);
check_status(status)?;
let type_id: *const TypeId = mem::transmute(unknown_tagged_object);
if *type_id == TypeId::of::<T>() {
let tagged_object: *mut TaggedObject<T> = mem::transmute(unknown_tagged_object);
(*tagged_object).object = None;
Ok(())
} else {
Err(Error {
status: Status::InvalidArg,
})
}
}
}
pub fn async_init(&self, resource: Option<Value<Object>>, name: &str) -> AsyncContext {
let raw_resource = resource
.map(|r| r.into_raw())
.unwrap_or_else(|| self.create_object().into_raw());
let raw_name = self.create_string(name).into_raw();
let mut raw_context = ptr::null_mut();
let mut raw_resource_ref = ptr::null_mut();
unsafe {
let status = sys::napi_async_init(self.0, raw_resource, raw_name, &mut raw_context);
debug_assert!(Status::from(status) == Status::Ok);
let status = sys::napi_create_reference(self.0, raw_resource, 1, &mut raw_resource_ref);
debug_assert!(Status::from(status) == Status::Ok);
}
AsyncContext {
raw_env: self.0,
raw_resource: raw_resource_ref,
raw_context,
}
}
pub fn create_promise(&self) -> (Value<Object>, Deferred) {
let mut raw_promise = ptr::null_mut();
let mut raw_deferred = ptr::null_mut();
unsafe {
sys::napi_create_promise(self.0, &mut raw_deferred, &mut raw_promise);
}
(Value::from_raw(self, raw_promise), Deferred(raw_deferred))
}
pub fn resolve_deferred<T: ValueType>(&self, deferred: Deferred, value: Value<T>) {
unsafe {
sys::napi_resolve_deferred(self.0, deferred.0, value.into_raw());
}
}
pub fn create_executor(&self) -> executor::LibuvExecutor {
let event_loop = unsafe { sys::uv_default_loop() };
executor::LibuvExecutor::new(event_loop)
}
}
pub trait ValueType: Copy {
fn matches_raw_type(raw_type: sys::napi_valuetype) -> bool;
}
impl ValueType for Any {
fn matches_raw_type(_raw_type: sys::napi_valuetype) -> bool {
true
}
}
impl ValueType for Undefined {
fn matches_raw_type(raw_type: sys::napi_valuetype) -> bool {
raw_type == sys::napi_valuetype::napi_undefined
}
}
impl ValueType for Boolean {
fn matches_raw_type(raw_type: sys::napi_valuetype) -> bool {
raw_type == sys::napi_valuetype::napi_boolean
}
}
impl ValueType for Number {
fn matches_raw_type(raw_type: sys::napi_valuetype) -> bool {
raw_type == sys::napi_valuetype::napi_number
}
}
impl ValueType for String {
fn matches_raw_type(raw_type: sys::napi_valuetype) -> bool {
raw_type == sys::napi_valuetype::napi_string
}
}
impl ValueType for Object {
fn matches_raw_type(raw_type: sys::napi_valuetype) -> bool {
raw_type == sys::napi_valuetype::napi_object
}
}
impl ValueType for Function {
fn matches_raw_type(raw_type: sys::napi_valuetype) -> bool {
raw_type == sys::napi_valuetype::napi_function
}
}
impl<'env, T: ValueType> Value<'env, T> {
pub fn from_raw(env: &'env Env, raw_value: sys::napi_value) -> Self {
Self {
env,
raw_value,
_marker: PhantomData,
}
}
pub fn into_raw(self) -> sys::napi_value {
self.raw_value
}
pub fn try_into<S: ValueType>(self) -> Result<Value<'env, S>> {
unsafe {
let mut value_type: sys::napi_valuetype = mem::uninitialized();
let status = sys::napi_typeof(self.env.0, self.raw_value, &mut value_type);
debug_assert!(Status::from(status) == Status::Ok);
if S::matches_raw_type(value_type) {
Ok(mem::transmute(self))
} else {
Err(Error {
status: Status::GenericFailure,
})
}
}
}
pub fn coerce_to_number(self) -> Result<Value<'env, Number>> {
let mut new_raw_value = ptr::null_mut();
let status =
unsafe { sys::napi_coerce_to_number(self.env.0, self.raw_value, &mut new_raw_value) };
check_status(status)?;
Ok(Value {
env: self.env,
raw_value: self.raw_value,
_marker: PhantomData,
})
}
pub fn coerce_to_string(self) -> Result<Value<'env, String>> {
let mut new_raw_value = ptr::null_mut();
let status =
unsafe { sys::napi_coerce_to_string(self.env.0, self.raw_value, &mut new_raw_value) };
check_status(status)?;
Ok(Value {
env: self.env,
raw_value: self.raw_value,
_marker: PhantomData,
})
}
pub fn coerce_to_object(self) -> Result<Value<'env, Object>> {
let mut new_raw_value = ptr::null_mut();
let status = unsafe {
sys::napi_coerce_to_object(
self.env.0,
self.raw_value,
(&mut new_raw_value) as *mut sys::napi_value,
)
};
check_status(status)?;
Ok(Value {
env: self.env,
raw_value: self.raw_value,
_marker: PhantomData,
})
}
}
impl<'env> Value<'env, String> {
pub fn len(&self) -> usize {
let mut raw_length = ptr::null_mut();
unsafe {
let status = sys::napi_get_named_property(
self.env.0,
self.raw_value,
"length\0".as_ptr() as *const c_char,
&mut raw_length,
);
debug_assert!(Status::from(status) == Status::Ok);
}
let length: Value<Number> = Value::from_raw(self.env, raw_length);
length.into()
}
}
impl<'env> Into<Vec<u16>> for Value<'env, String> {
fn into(self) -> Vec<u16> {
let mut result = Vec::with_capacity(self.len() + 1); // Leave room for trailing null byte
unsafe {
let mut written_char_count = 0;
let status = sys::napi_get_value_string_utf16(
self.env.0,
self.raw_value,
result.as_mut_ptr(),
result.capacity(),
&mut written_char_count,
);
debug_assert!(Status::from(status) == Status::Ok);
result.set_len(written_char_count);
}
result
}
}
impl<'env> Into<usize> for Value<'env, Number> {
fn into(self) -> usize {
let mut result = 0;
let status = unsafe { sys::napi_get_value_int64(self.env.0, self.raw_value, &mut result) };
debug_assert!(Status::from(status) == Status::Ok);
result as usize
}
}
impl<'env> Into<i64> for Value<'env, Number> {
fn into(self) -> i64 {
let mut result = 0;
let status = unsafe { sys::napi_get_value_int64(self.env.0, self.raw_value, &mut result) };
debug_assert!(Status::from(status) == Status::Ok);
result
}
}
impl<'env> Into<f64> for Value<'env, Number> {
fn into(self) -> f64 {
let mut result = 0_f64;
let status = unsafe { sys::napi_get_value_double(self.env.0, self.raw_value, &mut result) };
debug_assert!(Status::from(status) == Status::Ok);
result
}
}
impl<'env> Value<'env, Object> {
pub fn set_property<'a, K, V>(&mut self, key: Value<K>, value: Value<V>) -> Result<()> {
let status = unsafe {
sys::napi_set_property(
self.raw_env(),
self.raw_value(),
key.raw_value,
value.raw_value,
)
};
check_status(status)?;
Ok(())
}
pub fn set_named_property<'a, T, V: Into<Value<'a, T>>>(
&mut self,
name: &'a str,
value: V,
) -> Result<()> {
let key = CString::new(name)?;
let status = unsafe {
sys::napi_set_named_property(
self.raw_env(),
self.raw_value(),
key.as_ptr(),
value.into().raw_value,
)
};
check_status(status)?;
Ok(())
}
pub fn get_named_property<T: ValueType>(&self, name: &str) -> Result<Value<T>> {
let key = CString::new(name)?;
let mut raw_value = ptr::null_mut();
let status = unsafe {
sys::napi_get_named_property(
self.raw_env(),
self.raw_value(),
key.as_ptr(),
&mut raw_value,
)
};
check_status(status)?;
Value::<Any>::from_raw(self.env, raw_value).try_into()
}
pub fn set_index<'a, T>(&mut self, index: usize, value: Value<T>) -> Result<()> {
self.set_property(self.env.create_int64(index as i64), value)
}
fn raw_value(&self) -> sys::napi_value {
self.raw_value
}
fn raw_env(&self) -> sys::napi_env {
self.env.0
}
}
impl<'env> Value<'env, Function> {
pub fn call(
&self,
this: Option<&Value<'env, Object>>,
args: &[Value<'env, Any>],
) -> Result<Value<'env, Any>> {
let raw_this = this
.map(|v| v.into_raw())
.unwrap_or_else(|| self.env.get_undefined().into_raw());
let mut raw_args: [sys::napi_value; 8] = unsafe { mem::uninitialized() };
for (i, arg) in args.into_iter().enumerate() {
raw_args[i] = arg.raw_value;
}
let mut return_value = ptr::null_mut();
let status = unsafe {
sys::napi_call_function(
self.env.0,
raw_this,
self.raw_value,
args.len(),
&raw_args[0],
&mut return_value,
)
};
check_status(status)?;
Ok(Value::from_raw(self.env, return_value))
}
}
impl<T> Drop for Ref<T> {
fn drop(&mut self) {
unsafe {
let mut ref_count = 0;
let status = sys::napi_reference_unref(self.raw_env, self.raw_ref, &mut ref_count);
debug_assert!(Status::from(status) == Status::Ok);
if ref_count == 0 {
let status = sys::napi_delete_reference(self.raw_env, self.raw_ref);
debug_assert!(Status::from(status) == Status::Ok);
}
}
}
}
impl AsyncContext {
pub fn enter<'a, F: 'a + FnOnce(&mut Env)>(&'a self, run_in_context: F) {
let mut env = Env::from_raw(self.raw_env);
let mut handle_scope = ptr::null_mut();
let mut callback_scope = ptr::null_mut();
let mut raw_resource = ptr::null_mut();
unsafe {
sys::napi_open_handle_scope(env.0, &mut handle_scope);
sys::napi_get_reference_value(env.0, self.raw_resource, &mut raw_resource);
sys::extras_open_callback_scope(self.raw_context, raw_resource, &mut callback_scope);
}
run_in_context(&mut env);
unsafe {
sys::extras_close_callback_scope(callback_scope);
sys::napi_close_handle_scope(env.0, handle_scope);
}
}
}
impl Drop for AsyncContext {
fn drop(&mut self) {
unsafe {
sys::napi_delete_reference(self.raw_env, self.raw_resource);
}
}
}
impl Property {
pub fn new(name: &str) -> Self {
Property {
name: RustString::from(name),
raw_descriptor: sys::napi_property_descriptor {
utf8name: ptr::null_mut(),
name: ptr::null_mut(),
method: None,
getter: None,
setter: None,
value: ptr::null_mut(),
attributes: sys::napi_property_attributes::napi_default,
data: ptr::null_mut(),
},
}
}
pub fn with_value<T>(mut self, value: Value<T>) -> Self {
self.raw_descriptor.value = value.raw_value;
self
}
pub fn with_method(mut self, callback: Callback) -> Self {
self.raw_descriptor.method = Some(callback);
self
}
pub fn with_getter(mut self, callback: Callback) -> Self {
self.raw_descriptor.getter = Some(callback);
self
}
fn into_raw(mut self, env: &Env) -> sys::napi_property_descriptor {
self.raw_descriptor.name = env.create_string(&self.name).into_raw();
self.raw_descriptor
}
}
impl<T: 'static> TaggedObject<T> {
fn new(object: T) -> Self {
TaggedObject {
type_id: TypeId::of::<T>(),
object: Some(object),
}
}
}
fn check_status(code: sys::napi_status) -> Result<()> {
let status = Status::from(code);
match status {
Status::Ok => Ok(()),
_ => Err(Error { status }),
}
}
extern "C" fn raw_finalize<T>(
_raw_env: sys::napi_env,
finalize_data: *mut c_void,
_finalize_hint: *mut c_void,
) {
unsafe {
let tagged_object: *mut TaggedObject<T> = mem::transmute(finalize_data);
Box::from_raw(tagged_object);
}
}

27
src/sys/bindings.cc Normal file
View file

@ -0,0 +1,27 @@
#include "bindings.h"
#include <stdio.h>
#include <node.h>
#include <v8.h>
static v8::Local<v8::Value> V8LocalValueFromJsValue(napi_value v)
{
v8::Local<v8::Value> local;
memcpy(&local, &v, sizeof(v));
return local;
}
void extras_open_callback_scope(napi_async_context napi_async_context,
napi_value napi_resource_object,
extras_callback_scope *result)
{
v8::Isolate *isolate = v8::Isolate::GetCurrent();
v8::Local<v8::Context> context = isolate->GetCurrentContext();
v8::Local<v8::Object> resource_object = V8LocalValueFromJsValue(napi_resource_object)->ToObject(context).ToLocalChecked();
node::async_context *node_async_context = reinterpret_cast<node::async_context *>(napi_async_context);
*result = reinterpret_cast<extras_callback_scope>(new node::CallbackScope(isolate, resource_object, *node_async_context));
}
void extras_close_callback_scope(extras_callback_scope callback_scope)
{
delete reinterpret_cast<node::CallbackScope *>(callback_scope);
}

15
src/sys/bindings.h Normal file
View file

@ -0,0 +1,15 @@
#include <node_api.h>
#include <uv.h>
#include <string.h>
typedef struct extras_callback_scope__ *extras_callback_scope;
EXTERN_C_START
NAPI_EXTERN void extras_open_callback_scope(napi_async_context napi_async_context,
napi_value napi_resource_object,
extras_callback_scope *result);
NAPI_EXTERN void extras_close_callback_scope(extras_callback_scope callback_scope);
EXTERN_C_END

3
src/sys/bindings.rs Normal file

File diff suppressed because one or more lines are too long

17
src/sys/mod.rs Normal file
View file

@ -0,0 +1,17 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(dead_code)]
#![cfg_attr(feature = "cargo-clippy", allow(expl_impl_clone_on_copy))]
include!("bindings.rs");
#[cfg(node8)]
mod node8;
#[cfg(node8)]
pub use self::node8::Status;
#[cfg(node9)]
mod node9;
#[cfg(node9)]
pub use self::node9::Status;

44
src/sys/node8.rs Normal file
View file

@ -0,0 +1,44 @@
use super::napi_status;
#[derive(Eq, PartialEq, Debug)]
pub enum Status {
Ok,
InvalidArg,
ObjectExpected,
StringExpected,
NameExpected,
FunctionExpected,
NumberExpected,
BooleanExpected,
ArrayExpected,
GenericFailure,
PendingException,
Cancelled,
EscapeCalledTwice,
HandleScopeMismatch,
StringContainsNull,
}
impl From<napi_status> for Status {
fn from(code: napi_status) -> Self {
use self::napi_status::*;
use Status::*;
match code {
napi_ok => Ok,
napi_invalid_arg => InvalidArg,
napi_object_expected => ObjectExpected,
napi_string_expected => StringExpected,
napi_name_expected => NameExpected,
napi_function_expected => FunctionExpected,
napi_number_expected => NumberExpected,
napi_boolean_expected => BooleanExpected,
napi_array_expected => ArrayExpected,
napi_generic_failure => GenericFailure,
napi_pending_exception => PendingException,
napi_cancelled => Cancelled,
napi_escape_called_twice => EscapeCalledTwice,
napi_handle_scope_mismatch => HandleScopeMismatch,
}
}
}

46
src/sys/node9.rs Normal file
View file

@ -0,0 +1,46 @@
use super::napi_status;
#[derive(Eq, PartialEq, Debug)]
pub enum Status {
Ok,
InvalidArg,
ObjectExpected,
StringExpected,
NameExpected,
FunctionExpected,
NumberExpected,
BooleanExpected,
ArrayExpected,
GenericFailure,
PendingException,
Cancelled,
EscapeCalledTwice,
HandleScopeMismatch,
CallbackScopeMismatch,
StringContainsNull,
}
impl From<napi_status> for Status {
fn from(code: napi_status) -> Self {
use self::napi_status::*;
use Status::*;
match code {
napi_ok => Ok,
napi_invalid_arg => InvalidArg,
napi_object_expected => ObjectExpected,
napi_string_expected => StringExpected,
napi_name_expected => NameExpected,
napi_function_expected => FunctionExpected,
napi_number_expected => NumberExpected,
napi_boolean_expected => BooleanExpected,
napi_array_expected => ArrayExpected,
napi_generic_failure => GenericFailure,
napi_pending_exception => PendingException,
napi_cancelled => Cancelled,
napi_escape_called_twice => EscapeCalledTwice,
napi_handle_scope_mismatch => HandleScopeMismatch,
napi_callback_scope_mismatch => CallbackScopeMismatch,
}
}
}

10
test_module/Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "test-module"
version = "0.1.0"
authors = ["Nathan Sobo <nathan@github.com>"]
[lib]
crate-type = ["dylib"]
[dependencies]
napi-rs = {path = ".."}

13
test_module/package.json Normal file
View file

@ -0,0 +1,13 @@
{
"name": "test-module",
"version": "1.0.0",
"scripts": {
"build": "napi build",
"build-release": "napi build --release",
"check": "napi check",
"test": "node ./tests.js"
},
"dependencies": {
"napi-rs": ".."
}
}

73
test_module/src/lib.rs Normal file
View file

@ -0,0 +1,73 @@
#[macro_use]
extern crate napi_rs as napi;
use napi::{futures, Any, Env, Error, Object, Result, Status, Value};
register_module!(test_module, init);
fn init<'env>(
env: &'env Env,
exports: &'env mut Value<'env, Object>,
) -> Result<Option<Value<'env, Object>>> {
exports.set_named_property(
"testSpawn",
env.create_function("testSpawn", callback!(test_spawn)),
)?;
exports.set_named_property(
"testThrow",
env.create_function("testThrow", callback!(test_throw)),
)?;
Ok(None)
}
fn test_spawn<'a>(
env: &'a Env,
_this: Value<'a, Any>,
_args: &[Value<'a, Any>],
) -> Result<Option<Value<'a, Any>>> {
use futures::future::Executor;
use futures::{Future, Stream};
use std::{thread, time};
let async_context = env.async_init(None, "test_spawn");
let (promise, deferred) = env.create_promise();
let (tx, rx) = futures::sync::mpsc::unbounded();
let future = rx.for_each(|n: usize| {
println!("Received value {:?}", n);
futures::future::ok(())
}).and_then(move |_| {
async_context.enter(|env| {
env.resolve_deferred(deferred, env.get_undefined());
});
futures::future::ok(())
});
env.create_executor().execute(future).unwrap();
for _i in 0..10 {
let thread_tx = tx.clone();
thread::spawn(move || {
let mut n = 0;
loop {
println!("send {:?}", n);
thread_tx.unbounded_send(n).unwrap();
n += 1;
thread::sleep(time::Duration::from_millis(50));
if n == 10 {
break;
}
}
});
}
Ok(Some(promise.try_into().unwrap()))
}
fn test_throw<'a>(
_env: &'a Env,
_this: Value<'a, Any>,
_args: &[Value<'a, Any>],
) -> Result<Option<Value<'a, Any>>> {
Err(Error::new(Status::GenericFailure))
}

19
test_module/tests.js Normal file
View file

@ -0,0 +1,19 @@
const testModule = require('./target/debug/test_module')
function testSpawn() {
console.log('=== Test spawning a future on libuv event loop')
return testModule.testSpawn()
}
function testThrow() {
console.log('=== Test throwing from Rust')
try {
testModule.testThrow()
} catch (e) {
return
}
console.error('Expected function to throw an error')
process.exit(1)
}
testSpawn().then(testThrow)

12
test_module/yarn.lock Normal file
View file

@ -0,0 +1,12 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
minimist@^1.2.0:
version "1.2.0"
resolved "http://registry.npm.taobao.org/minimist/download/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
napi-rs@..:
version "0.1.0"
dependencies:
minimist "^1.2.0"

7
yarn.lock Normal file
View file

@ -0,0 +1,7 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
minimist@^1.2.0:
version "1.2.0"
resolved "http://registry.npm.taobao.org/minimist/download/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"