Skip to content

Commit

Permalink
Implement dynamic string size. (#110)
Browse files Browse the repository at this point in the history
* Implement dynamic string size.

* Add tests for string parameters.
  • Loading branch information
mattcollier authored Dec 23, 2020
1 parent cbef86c commit b44b981
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 36 deletions.
22 changes: 15 additions & 7 deletions examples/function/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@ use node_bindgen::core::NjError;


#[node_bindgen()]
fn hello(count: i32) -> String {
fn hello(count: i32) -> String {
format!("hello world {}", count)
}


#[node_bindgen]
fn sum(first: i32, second: i32) -> i32 {
fn sum(first: i32, second: i32) -> i32 {
first + second
}


// throw error if first > second, otherwise return sum
#[node_bindgen]
fn min_max(first: i32, second: i32) -> Result<i32,NjError> {
fn min_max(first: i32, second: i32) -> Result<i32,NjError> {
if first > second {
Err(NjError::Other("first arg is greater".to_owned()))
} else {
Expand All @@ -26,18 +26,26 @@ fn min_max(first: i32, second: i32) -> Result<i32,NjError> {
}

#[node_bindgen(name="multiply")]
fn mul(first: i32,second: i32) -> i32 {
first * second
fn mul(first: i32,second: i32) -> i32 {
first * second
}


/// add second if supplied
/// add second if supplied
#[node_bindgen()]
fn sum2(first: i32, second_arg: Option<i32>) -> i32 {
fn sum2(first: i32, second_arg: Option<i32>) -> i32 {
if let Some(second) = second_arg {
first + second
} else {
first
}
}

#[node_bindgen()]
fn string(first: String, second_arg: Option<String>) -> String {
if let Some(second) = second_arg {
format!("{} {}", first, second)
} else {
first
}
}
71 changes: 59 additions & 12 deletions examples/function/test.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,77 @@
const crypto = require('crypto');

let addon =require('./dist');
const assert = require('assert');

assert.equal(addon.hello(2),"hello world 2");

assert.strictEqual(addon.hello(2),"hello world 2");

assert.throws( () => addon.hello("hello"),{
message: 'invalid type, expected: number, actual: string'
});



assert.throws(() => addon.hello(),{
message: 'expected argument of type: i32'
});
});

assert.equal(addon.sum(1,2),3);
assert.strictEqual(addon.sum(1,2),3);

assert.throws( () => addon.minMax(10,0),{
message: 'first arg is greater',
message: 'first arg is greater',
});

assert.equal(addon.minMax(1,2),3);
assert.strictEqual(addon.minMax(1,2),3);

assert.strictEqual(addon.multiply(2,5),10);

assert.strictEqual(addon.sum2(10),10);
assert.strictEqual(addon.sum2(5,100),105);

const stringShort = _generateForCustomCharacters(5);
const stringMedium = _generateForCustomCharacters(100);
const stringLong = _generateForCustomCharacters(2000);
const strings = new Set([stringShort, stringMedium, stringLong]);

assert.strictEqual(addon.string(stringShort), stringShort);
assert.strictEqual(addon.string(stringMedium), stringMedium);
assert.strictEqual(addon.string(stringLong), stringLong);

for(const string1 in strings) {
for(const string2 in strings) {
assert.strictEqual(addon.string(string1), string2);
}
}

console.log("function tests succeed");

/*
* attribution: https://github.com/sindresorhus/crypto-random-string
* MIT License
* Copyright (c) Sindre Sorhus <[email protected]> (sindresorhus.com)
*/
function _generateForCustomCharacters(length, characters) {
characters = characters || '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'.split('');
// Generating entropy is faster than complex math operations, so we use the simplest way
const characterCount = characters.length;
const maxValidSelector = (Math.floor(0x10000 / characterCount) * characterCount) - 1; // Using values above this will ruin distribution when using modular division
const entropyLength = 2 * Math.ceil(1.1 * length); // Generating a bit more than required so chances we need more than one pass will be really low
let string = '';
let stringLength = 0;

while (stringLength < length) { // In case we had many bad values, which may happen for character sets of size above 0x8000 but close to it
const entropy = crypto.randomBytes(entropyLength);
let entropyPosition = 0;

assert.equal(addon.multiply(2,5),10);
while (entropyPosition < entropyLength && stringLength < length) {
const entropyValue = entropy.readUInt16LE(entropyPosition);
entropyPosition += 2;
if (entropyValue > maxValidSelector) { // Skip values which will ruin distribution when using modular division
continue;
}

assert.equal(addon.sum2(10),10);
assert.equal(addon.sum2(5,100),105);
string += characters[entropyValue % characterCount];
stringLength++;
}
}

console.log("function tests succeed");
return string;
}
44 changes: 27 additions & 17 deletions nj-core/src/convert.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use libc::size_t;
use std::ptr;

use crate::sys::napi_value;
use crate::val::JsEnv;
Expand All @@ -10,14 +11,14 @@ use crate::napi_call_result;
/// convert to JS object
pub trait TryIntoJs {

fn try_to_js(self,js_env: &JsEnv) -> Result<napi_value,NjError> ;
fn try_to_js(self,js_env: &JsEnv) -> Result<napi_value,NjError> ;

}

impl TryIntoJs for bool {
fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value,NjError> {
js_env.create_boolean(self)
}
}
}

impl TryIntoJs for f64 {
Expand Down Expand Up @@ -81,15 +82,15 @@ impl TryIntoJs for napi_value {

impl <T>TryIntoJs for Vec<T> where T: TryIntoJs {
fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value,NjError> {

let array = js_env.create_array_with_len(self.len())?;
for (i,element) in self.into_iter().enumerate() {
let js_element = element.try_to_js(js_env)?;
js_env.set_element(array, js_element, i)?;
}

Ok(array)
}
}
}


Expand All @@ -103,7 +104,7 @@ pub trait IntoJs {


/// Convert napi value to Rust value
///
///
pub trait JSValue<'a>: Sized {

fn label() -> &'static str {
Expand All @@ -130,7 +131,7 @@ impl JSValue<'_> for f64 {
}

impl JSValue<'_> for i32 {


fn convert_to_rust(env: &JsEnv,js_value: napi_value) -> Result<Self,NjError> {

Expand All @@ -147,7 +148,7 @@ impl JSValue<'_> for i32 {
}

impl JSValue<'_> for u32 {


fn convert_to_rust(env: &JsEnv,js_value: napi_value) -> Result<Self,NjError> {

Expand All @@ -165,7 +166,7 @@ impl JSValue<'_> for u32 {


impl JSValue<'_> for i64 {


fn convert_to_rust(env: &JsEnv,js_value: napi_value) -> Result<Self,NjError> {

Expand Down Expand Up @@ -208,14 +209,23 @@ impl JSValue<'_> for String {

use crate::sys::napi_get_value_string_utf8;

let mut chars: [u8; 1024] = [0;1024];
let mut size: size_t = 0;
let mut string_size: size_t = 0;

napi_call_result!(
napi_get_value_string_utf8(env.inner(),js_value,ptr::null_mut(),0,&mut string_size)
)?;

string_size += 1;

let chars_vec: Vec<u8> = vec![0; string_size];
let mut chars: Box<[u8]> = chars_vec.into_boxed_slice();
let mut read_size: size_t = 0;

napi_call_result!(
napi_get_value_string_utf8(env.inner(),js_value,chars.as_mut_ptr() as *mut i8,1024,&mut size)
napi_get_value_string_utf8(env.inner(),js_value,chars.as_mut_ptr() as *mut i8,string_size,&mut read_size)
)?;

let my_chars: Vec<u8> = chars[0..size].into();
let my_chars: Vec<u8> = chars[0..read_size].into();

String::from_utf8(my_chars).map_err(|err| err.into())
}
Expand All @@ -226,12 +236,12 @@ impl JSValue<'_> for String {
impl <'a,T>JSValue<'a> for Vec<T> where T: JSValue<'a> {

fn convert_to_rust(env: &'a JsEnv,js_value: napi_value) -> Result<Self,NjError> {

if !env.is_array(js_value)? {
return Err(NjError::Other("not array".to_owned()));
}


use crate::sys::napi_get_array_length;

let mut length: u32 = 0;
Expand All @@ -249,4 +259,4 @@ impl <'a,T>JSValue<'a> for Vec<T> where T: JSValue<'a> {

Ok(elements)
}
}
}

0 comments on commit b44b981

Please sign in to comment.