Skip to content

Commit b44b981

Browse files
authored
Implement dynamic string size. (#110)
* Implement dynamic string size. * Add tests for string parameters.
1 parent cbef86c commit b44b981

File tree

3 files changed

+101
-36
lines changed

3 files changed

+101
-36
lines changed

examples/function/src/lib.rs

+15-7
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,20 @@ use node_bindgen::core::NjError;
44

55

66
#[node_bindgen()]
7-
fn hello(count: i32) -> String {
7+
fn hello(count: i32) -> String {
88
format!("hello world {}", count)
99
}
1010

1111

1212
#[node_bindgen]
13-
fn sum(first: i32, second: i32) -> i32 {
13+
fn sum(first: i32, second: i32) -> i32 {
1414
first + second
1515
}
1616

1717

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

2828
#[node_bindgen(name="multiply")]
29-
fn mul(first: i32,second: i32) -> i32 {
30-
first * second
29+
fn mul(first: i32,second: i32) -> i32 {
30+
first * second
3131
}
3232

3333

34-
/// add second if supplied
34+
/// add second if supplied
3535
#[node_bindgen()]
36-
fn sum2(first: i32, second_arg: Option<i32>) -> i32 {
36+
fn sum2(first: i32, second_arg: Option<i32>) -> i32 {
3737
if let Some(second) = second_arg {
3838
first + second
3939
} else {
4040
first
4141
}
4242
}
4343

44+
#[node_bindgen()]
45+
fn string(first: String, second_arg: Option<String>) -> String {
46+
if let Some(second) = second_arg {
47+
format!("{} {}", first, second)
48+
} else {
49+
first
50+
}
51+
}

examples/function/test.js

+59-12
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,77 @@
1+
const crypto = require('crypto');
2+
13
let addon =require('./dist');
24
const assert = require('assert');
35

4-
assert.equal(addon.hello(2),"hello world 2");
5-
6+
assert.strictEqual(addon.hello(2),"hello world 2");
67

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

11-
12-
1312
assert.throws(() => addon.hello(),{
1413
message: 'expected argument of type: i32'
15-
});
14+
});
1615

17-
assert.equal(addon.sum(1,2),3);
16+
assert.strictEqual(addon.sum(1,2),3);
1817

1918
assert.throws( () => addon.minMax(10,0),{
20-
message: 'first arg is greater',
19+
message: 'first arg is greater',
2120
});
2221

23-
assert.equal(addon.minMax(1,2),3);
22+
assert.strictEqual(addon.minMax(1,2),3);
23+
24+
assert.strictEqual(addon.multiply(2,5),10);
25+
26+
assert.strictEqual(addon.sum2(10),10);
27+
assert.strictEqual(addon.sum2(5,100),105);
28+
29+
const stringShort = _generateForCustomCharacters(5);
30+
const stringMedium = _generateForCustomCharacters(100);
31+
const stringLong = _generateForCustomCharacters(2000);
32+
const strings = new Set([stringShort, stringMedium, stringLong]);
33+
34+
assert.strictEqual(addon.string(stringShort), stringShort);
35+
assert.strictEqual(addon.string(stringMedium), stringMedium);
36+
assert.strictEqual(addon.string(stringLong), stringLong);
37+
38+
for(const string1 in strings) {
39+
for(const string2 in strings) {
40+
assert.strictEqual(addon.string(string1), string2);
41+
}
42+
}
43+
44+
console.log("function tests succeed");
45+
46+
/*
47+
* attribution: https://github.com/sindresorhus/crypto-random-string
48+
* MIT License
49+
* Copyright (c) Sindre Sorhus <[email protected]> (sindresorhus.com)
50+
*/
51+
function _generateForCustomCharacters(length, characters) {
52+
characters = characters || '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'.split('');
53+
// Generating entropy is faster than complex math operations, so we use the simplest way
54+
const characterCount = characters.length;
55+
const maxValidSelector = (Math.floor(0x10000 / characterCount) * characterCount) - 1; // Using values above this will ruin distribution when using modular division
56+
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
57+
let string = '';
58+
let stringLength = 0;
59+
60+
while (stringLength < length) { // In case we had many bad values, which may happen for character sets of size above 0x8000 but close to it
61+
const entropy = crypto.randomBytes(entropyLength);
62+
let entropyPosition = 0;
2463

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

27-
assert.equal(addon.sum2(10),10);
28-
assert.equal(addon.sum2(5,100),105);
71+
string += characters[entropyValue % characterCount];
72+
stringLength++;
73+
}
74+
}
2975

30-
console.log("function tests succeed");
76+
return string;
77+
}

nj-core/src/convert.rs

+27-17
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use libc::size_t;
2+
use std::ptr;
23

34
use crate::sys::napi_value;
45
use crate::val::JsEnv;
@@ -10,14 +11,14 @@ use crate::napi_call_result;
1011
/// convert to JS object
1112
pub trait TryIntoJs {
1213

13-
fn try_to_js(self,js_env: &JsEnv) -> Result<napi_value,NjError> ;
14-
14+
fn try_to_js(self,js_env: &JsEnv) -> Result<napi_value,NjError> ;
15+
1516
}
1617

1718
impl TryIntoJs for bool {
1819
fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value,NjError> {
1920
js_env.create_boolean(self)
20-
}
21+
}
2122
}
2223

2324
impl TryIntoJs for f64 {
@@ -81,15 +82,15 @@ impl TryIntoJs for napi_value {
8182

8283
impl <T>TryIntoJs for Vec<T> where T: TryIntoJs {
8384
fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value,NjError> {
84-
85+
8586
let array = js_env.create_array_with_len(self.len())?;
8687
for (i,element) in self.into_iter().enumerate() {
8788
let js_element = element.try_to_js(js_env)?;
8889
js_env.set_element(array, js_element, i)?;
8990
}
9091

9192
Ok(array)
92-
}
93+
}
9394
}
9495

9596

@@ -103,7 +104,7 @@ pub trait IntoJs {
103104

104105

105106
/// Convert napi value to Rust value
106-
///
107+
///
107108
pub trait JSValue<'a>: Sized {
108109

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

132133
impl JSValue<'_> for i32 {
133-
134+
134135

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

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

149150
impl JSValue<'_> for u32 {
150-
151+
151152

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

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

166167

167168
impl JSValue<'_> for i64 {
168-
169+
169170

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

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

209210
use crate::sys::napi_get_value_string_utf8;
210211

211-
let mut chars: [u8; 1024] = [0;1024];
212-
let mut size: size_t = 0;
212+
let mut string_size: size_t = 0;
213+
214+
napi_call_result!(
215+
napi_get_value_string_utf8(env.inner(),js_value,ptr::null_mut(),0,&mut string_size)
216+
)?;
217+
218+
string_size += 1;
219+
220+
let chars_vec: Vec<u8> = vec![0; string_size];
221+
let mut chars: Box<[u8]> = chars_vec.into_boxed_slice();
222+
let mut read_size: size_t = 0;
213223

214224
napi_call_result!(
215-
napi_get_value_string_utf8(env.inner(),js_value,chars.as_mut_ptr() as *mut i8,1024,&mut size)
225+
napi_get_value_string_utf8(env.inner(),js_value,chars.as_mut_ptr() as *mut i8,string_size,&mut read_size)
216226
)?;
217227

218-
let my_chars: Vec<u8> = chars[0..size].into();
228+
let my_chars: Vec<u8> = chars[0..read_size].into();
219229

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

228238
fn convert_to_rust(env: &'a JsEnv,js_value: napi_value) -> Result<Self,NjError> {
229-
239+
230240
if !env.is_array(js_value)? {
231241
return Err(NjError::Other("not array".to_owned()));
232242
}
233-
234-
243+
244+
235245
use crate::sys::napi_get_array_length;
236246

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

250260
Ok(elements)
251261
}
252-
}
262+
}

0 commit comments

Comments
 (0)