1use std::ptr;
2
3use tracing::debug;
4
5use crate::sys::napi_value;
6use crate::val::JsEnv;
7use crate::NjError;
8use crate::napi_call_result;
9
10pub trait TryIntoJs {
12 fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value, NjError>;
13}
14
15impl TryIntoJs for bool {
16 fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value, NjError> {
17 js_env.create_boolean(self)
18 }
19}
20
21impl TryIntoJs for f64 {
22 fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value, NjError> {
23 js_env.create_double(self)
24 }
25}
26
27impl TryIntoJs for i8 {
28 fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value, NjError> {
29 js_env.create_int32(self as i32)
30 }
31}
32
33impl TryIntoJs for i16 {
34 fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value, NjError> {
35 js_env.create_int32(self as i32)
36 }
37}
38
39impl TryIntoJs for i32 {
40 fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value, NjError> {
41 js_env.create_int32(self)
42 }
43}
44
45impl TryIntoJs for i64 {
46 fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value, NjError> {
47 js_env.create_int64(self)
48 }
49}
50
51impl TryIntoJs for u8 {
52 fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value, NjError> {
53 js_env.create_uint32(self as u32)
54 }
55}
56
57impl TryIntoJs for u16 {
58 fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value, NjError> {
59 js_env.create_uint32(self as u32)
60 }
61}
62
63impl TryIntoJs for u32 {
64 fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value, NjError> {
65 js_env.create_uint32(self)
66 }
67}
68
69impl TryIntoJs for u64 {
70 fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value, NjError> {
71 js_env.create_bigint_uint64(self)
72 }
73}
74
75impl TryIntoJs for usize {
76 fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value, NjError> {
77 js_env.create_bigint_uint64(self as u64)
78 }
79}
80
81impl TryIntoJs for String {
82 fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value, NjError> {
83 js_env.create_string_utf8(&self)
84 }
85}
86
87impl TryIntoJs for () {
88 fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value, NjError> {
89 js_env.get_undefined()
90 }
91}
92
93impl TryIntoJs for NjError {
94 fn try_to_js(self, _js_env: &JsEnv) -> Result<napi_value, NjError> {
95 Err(self)
97 }
98}
99
100impl TryIntoJs for std::io::Error {
101 fn try_to_js(self, _js_env: &JsEnv) -> Result<napi_value, NjError> {
102 let message = self.to_string();
103 Err(NjError::Other(message))
104 }
105}
106
107#[cfg(feature = "serde_json")]
108impl TryIntoJs for serde_json::Value {
109 fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value, NjError> {
110 match self {
111 serde_json::Value::Null => js_env.get_null(),
112 serde_json::Value::Bool(val) => val.try_to_js(js_env),
113 serde_json::Value::Number(num) => {
114 if num.is_i64() {
115 js_env.create_int64(num.as_i64().unwrap())
116 } else if num.is_u64() {
117 js_env.create_bigint_uint64(num.as_u64().unwrap())
118 } else {
119 js_env.create_double(num.as_f64().unwrap())
120 }
121 }
122 serde_json::Value::String(string) => string.try_to_js(js_env),
123 serde_json::Value::Array(arr) => arr.try_to_js(js_env),
124 serde_json::Value::Object(obj) => obj.try_to_js(js_env),
125 }
126 }
127}
128
129#[cfg(feature = "convert-uuid")]
130impl TryIntoJs for uuid::Uuid {
131 fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value, NjError> {
132 let as_str = self
133 .as_hyphenated()
134 .encode_lower(&mut uuid::Uuid::encode_buffer())
135 .to_string();
136
137 as_str.try_to_js(js_env)
138 }
139}
140
141#[cfg(feature = "convert-uuid")]
142impl JSValue<'_> for uuid::Uuid {
143 fn convert_to_rust(env: &JsEnv, js_value: napi_value) -> Result<Self, NjError> {
144 let string = String::convert_to_rust(env, js_value)?;
145 let uuid = uuid::Uuid::parse_str(&string)
146 .map_err(|e| NjError::Other(format!("Failed to parse Uuid: {e}")))?;
147 Ok(uuid)
148 }
149}
150
151impl<T, E> TryIntoJs for Result<T, E>
152where
153 T: TryIntoJs,
154 E: TryIntoJs,
155{
156 fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value, NjError> {
157 match self {
158 Ok(val) => val.try_to_js(js_env),
159 Err(err) => Err(NjError::Native(err.try_to_js(js_env)?)),
160 }
161 }
162}
163
164impl<T> TryIntoJs for Option<T>
165where
166 T: TryIntoJs,
167{
168 fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value, NjError> {
169 match self {
170 Some(val) => val.try_to_js(js_env),
171 None => js_env.get_null(),
172 }
173 }
174}
175
176impl TryIntoJs for napi_value {
177 fn try_to_js(self, _js_env: &JsEnv) -> Result<napi_value, NjError> {
178 Ok(self)
179 }
180}
181
182impl<T> TryIntoJs for Vec<T>
183where
184 T: TryIntoJs,
185{
186 fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value, NjError> {
187 let array = js_env.create_array_with_len(self.len())?;
188 for (i, element) in self.into_iter().enumerate() {
189 let js_element = element.try_to_js(js_env)?;
190 js_env.set_element(array, js_element, i)?;
191 }
192
193 Ok(array)
194 }
195}
196
197#[cfg(feature = "serde_json")]
198impl TryIntoJs for serde_json::map::Map<String, serde_json::Value> {
199 fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value, NjError> {
200 use crate::val::JsObject;
201 let mut obj = JsObject::new(*js_env, js_env.create_object()?);
202
203 let converted_obj = self
204 .into_iter()
205 .map(|(key, val)| val.try_to_js(js_env).map(|v| (key, v)))
206 .collect::<Result<Vec<(String, napi_value)>, NjError>>()?;
207
208 for (key, val) in converted_obj {
209 obj.set_property(&key, val)?;
210 }
211
212 Ok(obj.napi_value())
213 }
214}
215
216pub trait IntoJs {
218 fn into_js(self, js_env: &JsEnv) -> napi_value;
219}
220
221pub trait JSValue<'a>: Sized {
224 fn label() -> &'static str {
225 std::any::type_name::<Self>()
226 }
227
228 fn convert_to_rust(env: &'a JsEnv, js_value: napi_value) -> Result<Self, NjError>;
229}
230
231impl JSValue<'_> for f64 {
232 #[allow(clippy::not_unsafe_ptr_arg_deref)]
233 fn convert_to_rust(env: &JsEnv, js_value: napi_value) -> Result<Self, NjError> {
234 debug!("convert_to_rust: f64");
235 env.assert_type(js_value, crate::sys::napi_valuetype_napi_number)?;
236
237 let mut value: f64 = 0.0;
238
239 napi_call_result!(crate::sys::napi_get_value_double(
240 env.inner(),
241 js_value,
242 &mut value
243 ))?;
244
245 Ok(value)
246 }
247}
248
249impl JSValue<'_> for i32 {
250 #[allow(clippy::not_unsafe_ptr_arg_deref)]
251 fn convert_to_rust(env: &JsEnv, js_value: napi_value) -> Result<Self, NjError> {
252 env.assert_type(js_value, crate::sys::napi_valuetype_napi_number)?;
253
254 let mut value: i32 = 0;
255
256 napi_call_result!(crate::sys::napi_get_value_int32(
257 env.inner(),
258 js_value,
259 &mut value
260 ))?;
261
262 Ok(value)
263 }
264}
265
266impl JSValue<'_> for u32 {
267 #[allow(clippy::not_unsafe_ptr_arg_deref)]
268 fn convert_to_rust(env: &JsEnv, js_value: napi_value) -> Result<Self, NjError> {
269 env.assert_type(js_value, crate::sys::napi_valuetype_napi_number)?;
270
271 let mut value: u32 = 0;
272
273 napi_call_result!(crate::sys::napi_get_value_uint32(
274 env.inner(),
275 js_value,
276 &mut value
277 ))?;
278
279 Ok(value)
280 }
281}
282
283impl JSValue<'_> for i64 {
284 #[allow(clippy::not_unsafe_ptr_arg_deref)]
285 fn convert_to_rust(env: &JsEnv, js_value: napi_value) -> Result<Self, NjError> {
286 env.assert_type(js_value, crate::sys::napi_valuetype_napi_number)?;
287
288 let mut value: i64 = 0;
289
290 napi_call_result!(crate::sys::napi_get_value_int64(
291 env.inner(),
292 js_value,
293 &mut value
294 ))?;
295
296 Ok(value)
297 }
298}
299
300impl JSValue<'_> for bool {
301 #[allow(clippy::not_unsafe_ptr_arg_deref)]
302 fn convert_to_rust(env: &JsEnv, js_value: napi_value) -> Result<Self, NjError> {
303 env.assert_type(js_value, crate::sys::napi_valuetype_napi_boolean)?;
304
305 let mut value: bool = false;
306
307 napi_call_result!(crate::sys::napi_get_value_bool(
308 env.inner(),
309 js_value,
310 &mut value
311 ))?;
312
313 Ok(value)
314 }
315}
316
317impl JSValue<'_> for String {
318 #[allow(clippy::not_unsafe_ptr_arg_deref)]
319 fn convert_to_rust(env: &JsEnv, js_value: napi_value) -> Result<Self, NjError> {
320 env.assert_type(js_value, crate::sys::napi_valuetype_napi_string)?;
321
322 use crate::sys::napi_get_value_string_utf8;
323
324 let mut string_size: usize = 0;
325
326 napi_call_result!(napi_get_value_string_utf8(
327 env.inner(),
328 js_value,
329 ptr::null_mut(),
330 0,
331 &mut string_size
332 ))?;
333
334 string_size += 1;
335
336 let chars_vec: Vec<u8> = vec![0; string_size];
337 let mut chars: Box<[u8]> = chars_vec.into_boxed_slice();
338 let mut read_size: usize = 0;
339
340 napi_call_result!(napi_get_value_string_utf8(
341 env.inner(),
342 js_value,
343 chars.as_mut_ptr() as *mut ::std::os::raw::c_char,
344 string_size,
345 &mut read_size
346 ))?;
347
348 let my_chars: Vec<u8> = chars[0..read_size].into();
349
350 String::from_utf8(my_chars).map_err(|err| err.into())
351 }
352}
353
354impl<'a> JSValue<'a> for &'a str {
355 #[allow(clippy::not_unsafe_ptr_arg_deref)]
356 fn convert_to_rust(env: &'a JsEnv, js_value: napi_value) -> Result<Self, NjError> {
357 use crate::sys::napi_get_buffer_info;
358
359 let mut len: usize = 0;
360 let mut data = ptr::null_mut();
361
362 napi_call_result!(napi_get_buffer_info(
363 env.inner(),
364 js_value,
365 &mut data,
366 &mut len
367 ))?;
368
369 unsafe {
370 let i8slice = std::slice::from_raw_parts(data as *mut ::std::os::raw::c_char, len);
371 let u8slice = &*(i8slice as *const _ as *const [u8]);
372 std::str::from_utf8(u8slice).map_err(|err| err.into())
373 }
374 }
375}
376
377impl<'a, T> JSValue<'a> for Vec<T>
378where
379 T: JSValue<'a>,
380{
381 #[allow(clippy::not_unsafe_ptr_arg_deref)]
382 fn convert_to_rust(env: &'a JsEnv, js_value: napi_value) -> Result<Self, NjError> {
383 if !env.is_array(js_value)? {
384 return Err(NjError::Other(
385 "Provided value was not an array as expected".to_owned(),
386 ));
387 }
388
389 use crate::sys::napi_get_array_length;
390
391 let mut length: u32 = 0;
392
393 napi_call_result!(napi_get_array_length(env.inner(), js_value, &mut length))?;
394
395 let mut elements = vec![];
396
397 for i in 0..length {
398 let js_element = env.get_element(js_value, i)?;
399 elements.push(T::convert_to_rust(env, js_element)?);
400 }
401
402 Ok(elements)
403 }
404}
405
406macro_rules! impl_js_value_for_tuple {
407 ( $( $len:expr => ( $( $n:tt $t:ident ),+ $(,)? ))+ ) => {
408 $(
409 impl<'a $(, $t)+ > crate::JSValue<'a> for ($($t,)+)
410 where
411 $($t: JSValue<'a> + Send,)+
412 {
413 #[allow(clippy::not_unsafe_ptr_arg_deref)]
414 fn convert_to_rust(env: &'a JsEnv, js_value: napi_value) -> Result<Self, NjError> {
415 use crate::sys::napi_get_array_length;
416 if !env.is_array(js_value)? {
417 return Err(NjError::Other("Tuples must come from JS arrays".to_owned()));
418 }
419
420 let mut length: u32 = 0;
421 napi_call_result!(napi_get_array_length(env.inner(), js_value, &mut length))?;
422 let required_length = $len;
423 if length != required_length {
424 return Err(NjError::Other(format!("{n}Tuple must have exactly length {n}", n = required_length)));
425 }
426
427 $(
428 let js_element = env.get_element(js_value, $n)?;
429 #[allow(non_snake_case)]
430 let $t = $t::convert_to_rust(env, js_element)?;
431 )+
432
433 Ok(( $($t,)+ ))
434 }
435 }
436 )+
437 }
438}
439
440impl_js_value_for_tuple! {
441 1 => (0 T0)
442 2 => (0 T0, 1 T1)
443 3 => (0 T0, 1 T1, 2 T2)
444 4 => (0 T0, 1 T1, 2 T2, 3 T3)
445 5 => (0 T0, 1 T1, 2 T2, 3 T3, 4 T4)
446 6 => (0 T0, 1 T1, 2 T2, 3 T3, 4 T4, 5 T5)
447 7 => (0 T0, 1 T1, 2 T2, 3 T3, 4 T4, 5 T5, 6 T6)
448 8 => (0 T0, 1 T1, 2 T2, 3 T3, 4 T4, 5 T5, 6 T6, 7 T7)
449 9 => (0 T0, 1 T1, 2 T2, 3 T3, 4 T4, 5 T5, 6 T6, 7 T7, 8 T8)
450}
451
452macro_rules! impl_try_into_js_for_tuple {
453 ( $( $len:expr => ( $( $n:tt $t:tt ),+ $(,)? ))+ ) => {
454 $(
455 impl<$( $t ),+> crate::TryIntoJs for ( $( $t, )+ )
456 where $( $t: TryIntoJs + Send, )+
457 {
458 fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value, NjError> {
459 let length: usize = $len;
460 let array = js_env.create_array_with_len(length)?;
461
462 #[allow(non_snake_case)]
463 let ( $($t, )+ ) = self;
464
465 $(
466 let js_element = $t.try_to_js(js_env)?;
467 js_env.set_element(array, js_element, $n)?;
468 )+
469
470 Ok(array)
471 }
472 }
473 )+
474 }
475}
476
477impl_try_into_js_for_tuple! {
478 1 => (0 T0)
479 2 => (0 T0, 1 T1)
480 3 => (0 T0, 1 T1, 2 T2)
481 4 => (0 T0, 1 T1, 2 T2, 3 T3)
482 5 => (0 T0, 1 T1, 2 T2, 3 T3, 4 T4)
483 6 => (0 T0, 1 T1, 2 T2, 3 T3, 4 T4, 5 T5)
484 7 => (0 T0, 1 T1, 2 T2, 3 T3, 4 T4, 5 T5, 6 T6)
485 8 => (0 T0, 1 T1, 2 T2, 3 T3, 4 T4, 5 T5, 6 T6, 7 T7)
486 9 => (0 T0, 1 T1, 2 T2, 3 T3, 4 T4, 5 T5, 6 T6, 7 T7, 8 T8)
487}