neon/types_impl/extract/
try_from_js.rs

1// Implementations in this file are equivalent to a call to `.downcast()` and
2// `.value(&mut cx)`. These specialized versions provide a performance benefit
3// because they can combine two Node-API calls into a single call that both
4// gets the value and checks the type at the same time.
5
6use std::{convert::Infallible, ptr};
7
8use crate::{
9    context::{internal::ContextInternal, Cx},
10    handle::{Handle, Root},
11    object::Object,
12    result::{NeonResult, Throw},
13    sys,
14    types::{
15        extract::{Date, TryFromJs, TypeExpected},
16        private::ValueInternal,
17        JsBoolean, JsNumber, JsString, JsValue, Value,
18    },
19};
20
21#[cfg(feature = "napi-5")]
22use crate::types::JsDate;
23
24impl<'cx, V> TryFromJs<'cx> for Handle<'cx, V>
25where
26    V: Value,
27{
28    type Error = TypeExpected<V>;
29
30    fn try_from_js(
31        cx: &mut Cx<'cx>,
32        v: Handle<'cx, JsValue>,
33    ) -> NeonResult<Result<Self, Self::Error>> {
34        Ok(v.downcast(cx).map_err(|_| TypeExpected::new()))
35    }
36}
37
38impl<'cx, O> TryFromJs<'cx> for Root<O>
39where
40    O: Object,
41{
42    type Error = TypeExpected<O>;
43
44    fn try_from_js(
45        cx: &mut Cx<'cx>,
46        v: Handle<'cx, JsValue>,
47    ) -> NeonResult<Result<Self, Self::Error>> {
48        Ok(match v.downcast::<O, _>(cx) {
49            Ok(v) => Ok(v.root(cx)),
50            Err(_) => Err(TypeExpected::new()),
51        })
52    }
53}
54
55impl<'cx, T> TryFromJs<'cx> for Option<T>
56where
57    T: TryFromJs<'cx>,
58{
59    type Error = T::Error;
60
61    fn try_from_js(
62        cx: &mut Cx<'cx>,
63        v: Handle<'cx, JsValue>,
64    ) -> NeonResult<Result<Self, Self::Error>> {
65        if is_null_or_undefined(cx, v)? {
66            return Ok(Ok(None));
67        }
68
69        T::try_from_js(cx, v).map(|v| v.map(Some))
70    }
71}
72
73impl<'cx> TryFromJs<'cx> for f64 {
74    type Error = TypeExpected<JsNumber>;
75
76    fn try_from_js(
77        cx: &mut Cx<'cx>,
78        v: Handle<'cx, JsValue>,
79    ) -> NeonResult<Result<Self, Self::Error>> {
80        let mut n = 0f64;
81
82        unsafe {
83            match sys::get_value_double(cx.env().to_raw(), v.to_local(), &mut n) {
84                Err(sys::Status::NumberExpected) => return Ok(Err(TypeExpected::new())),
85                Err(sys::Status::PendingException) => return Err(Throw::new()),
86                status => status.unwrap(),
87            };
88        }
89
90        Ok(Ok(n))
91    }
92}
93
94impl<'cx> TryFromJs<'cx> for bool {
95    type Error = TypeExpected<JsBoolean>;
96
97    fn try_from_js(
98        cx: &mut Cx<'cx>,
99        v: Handle<'cx, JsValue>,
100    ) -> NeonResult<Result<Self, Self::Error>> {
101        let mut b = false;
102
103        unsafe {
104            match sys::get_value_bool(cx.env().to_raw(), v.to_local(), &mut b) {
105                Err(sys::Status::BooleanExpected) => return Ok(Err(TypeExpected::new())),
106                Err(sys::Status::PendingException) => return Err(Throw::new()),
107                status => status.unwrap(),
108            };
109        }
110
111        Ok(Ok(b))
112    }
113}
114
115impl<'cx> TryFromJs<'cx> for String {
116    type Error = TypeExpected<JsString>;
117
118    fn try_from_js(
119        cx: &mut Cx<'cx>,
120        v: Handle<'cx, JsValue>,
121    ) -> NeonResult<Result<Self, Self::Error>> {
122        let env = cx.env().to_raw();
123        let v = v.to_local();
124        let mut len = 0usize;
125
126        unsafe {
127            match sys::get_value_string_utf8(env, v, ptr::null_mut(), 0, &mut len) {
128                Err(sys::Status::StringExpected) => return Ok(Err(TypeExpected::new())),
129                Err(sys::Status::PendingException) => return Err(Throw::new()),
130                status => status.unwrap(),
131            };
132        }
133
134        // Make room for null terminator to avoid losing a character
135        let mut buf = Vec::<u8>::with_capacity(len + 1);
136        let mut written = 0usize;
137
138        unsafe {
139            assert_eq!(
140                sys::get_value_string_utf8(
141                    env,
142                    v,
143                    buf.as_mut_ptr().cast(),
144                    buf.capacity(),
145                    &mut written,
146                ),
147                Ok(())
148            );
149
150            debug_assert_eq!(len, written);
151            buf.set_len(len);
152
153            Ok(Ok(String::from_utf8_unchecked(buf)))
154        }
155    }
156}
157
158#[cfg_attr(docsrs, doc(cfg(feature = "napi-5")))]
159#[cfg(feature = "napi-5")]
160impl<'cx> TryFromJs<'cx> for Date {
161    type Error = TypeExpected<JsDate>;
162
163    fn try_from_js(
164        cx: &mut Cx<'cx>,
165        v: Handle<'cx, JsValue>,
166    ) -> NeonResult<Result<Self, Self::Error>> {
167        let mut d = 0f64;
168
169        unsafe {
170            match sys::get_date_value(cx.env().to_raw(), v.to_local(), &mut d) {
171                Err(sys::Status::DateExpected) => return Ok(Err(TypeExpected::new())),
172                Err(sys::Status::PendingException) => return Err(Throw::new()),
173                status => status.unwrap(),
174            };
175        }
176
177        Ok(Ok(Date(d)))
178    }
179}
180
181// This implementation primarily exists for macro authors. It is infallible, rather
182// than checking a type, to match the JavaScript conventions of ignoring additional
183// arguments.
184//
185// N.B.: There is a blanket impl of `FromArgs` for `T` where `T: TryFromJs` to make
186// the common case of `arity == 1` more ergonomic and avoid `(T)` is *not* a tuple
187// foot-gun (but, `(T,)` is). This creates ambiguity for `()`. Are we extracting
188// unit from the first argument of a function with `arity == 1` or is this a function
189// with `arity == 0`? By making extraction of unit infallible, we eliminate any
190// impact from the ambiguity.
191impl<'cx> TryFromJs<'cx> for () {
192    type Error = Infallible;
193
194    fn try_from_js(
195        _cx: &mut Cx<'cx>,
196        _v: Handle<'cx, JsValue>,
197    ) -> NeonResult<Result<Self, Self::Error>> {
198        Ok(Ok(()))
199    }
200}
201
202fn is_null_or_undefined<V>(cx: &mut Cx, v: Handle<V>) -> NeonResult<bool>
203where
204    V: Value,
205{
206    let mut ty = sys::ValueType::Object;
207
208    unsafe {
209        match sys::typeof_value(cx.env().to_raw(), v.to_local(), &mut ty) {
210            Err(sys::Status::PendingException) => return Err(Throw::new()),
211            status => status.unwrap(),
212        };
213    }
214
215    Ok(matches!(
216        ty,
217        sys::ValueType::Undefined | sys::ValueType::Null,
218    ))
219}