nj_core/
convert.rs

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
10/// convert to JS object
11pub 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        // Re-throw the error into JS
96        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
216/// convert to js including error
217pub trait IntoJs {
218    fn into_js(self, js_env: &JsEnv) -> napi_value;
219}
220
221/// Convert napi value to Rust value
222///
223pub 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}