Skip to main content

neon/types_impl/extract/
array.rs

1use std::{error, fmt, mem::MaybeUninit};
2
3use crate::{
4    context::{internal::ContextInternal, Context, Cx},
5    handle::Handle,
6    result::{JsResult, NeonResult, Throw},
7    sys,
8    types::{
9        extract::{private, TryFromJs, TryIntoJs, TypeExpected},
10        private::ValueInternal,
11        JsArray, JsValue,
12    },
13};
14
15/// Extracts a [JavaScript array](JsArray) into a Rust collection or converts a collection to a JS array.
16///
17/// Any collection that implements [`FromIterator`] and [`IntoIterator`] can be extracted. Extraction
18/// fails with [`ArrayError`] if the value is not an array or an element fails to be extracted.
19///
20/// # Example
21///
22/// ```
23/// # use std::collections::HashSet;
24/// # use neon::types::extract::Array;
25/// #[neon::export]
26/// fn list_of_strings(Array(arr): Array<Vec<String>>) -> Array<Vec<String>> {
27///     Array(arr)
28/// }
29///
30/// #[neon::export]
31/// fn double(Array(arr): Array<Vec<f64>>) -> Array<impl Iterator<Item = f64>> {
32///     Array(arr.into_iter().map(|x| x * 2.0))
33/// }
34///
35/// #[neon::export]
36/// fn dedupe(set: Array<HashSet<String>>) -> Array<HashSet<String>> {
37///     set
38/// }
39/// ```
40///
41/// **Note**: Only native JS arrays are accepted. For typed arrays use [`Uint8Array`](super::Uint8Array),
42/// [`Float64Array`](super::Float64Array), etc.
43pub struct Array<T>(pub T);
44
45impl<T> private::Sealed for Array<T> {}
46
47impl<'cx, T> TryFromJs<'cx> for Array<T>
48where
49    T: FromIterator<T::Item>,
50    T: IntoIterator,
51    T::Item: TryFromJs<'cx>,
52{
53    type Error = ArrayError<<T::Item as TryFromJs<'cx>>::Error>;
54
55    fn try_from_js(
56        cx: &mut Cx<'cx>,
57        v: Handle<'cx, JsValue>,
58    ) -> NeonResult<Result<Self, Self::Error>> {
59        let env = cx.env().to_raw();
60        let v = v.to_local();
61        let len = unsafe {
62            let mut len = 0;
63
64            match sys::get_array_length(env, v, &mut len) {
65                Err(sys::Status::PendingException) => return Err(Throw::new()),
66                Err(sys::Status::ArrayExpected) => return Ok(Err(ArrayError::array())),
67                res => res.unwrap(),
68            }
69
70            len
71        };
72
73        (0..len)
74            .map(|i| {
75                let item = unsafe {
76                    let mut item = MaybeUninit::uninit();
77
78                    match sys::get_element(env, v, i, item.as_mut_ptr()) {
79                        Err(sys::Status::PendingException) => return Err(Throw::new()),
80                        res => res.unwrap(),
81                    }
82
83                    Handle::new_internal(JsValue::from_local(cx.env(), item.assume_init()))
84                };
85
86                match T::Item::try_from_js(cx, item) {
87                    Ok(Ok(item)) => Ok(Ok(item)),
88                    Ok(Err(err)) => Ok(Err(ArrayError::item(err))),
89                    Err(err) => Err(err),
90                }
91            })
92            .collect::<Result<Result<T, Self::Error>, Throw>>()
93            .map(|v| v.map(Array))
94    }
95}
96
97impl<'cx, T> TryIntoJs<'cx> for Array<T>
98where
99    T: IntoIterator,
100    T::Item: TryIntoJs<'cx>,
101{
102    type Value = JsArray;
103
104    fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> {
105        let iter = self.0.into_iter();
106        let env = cx.env().to_raw();
107        let (len, _) = iter.size_hint();
108        let arr = unsafe {
109            let mut arr = MaybeUninit::uninit();
110
111            match sys::create_array_with_length(env, len, arr.as_mut_ptr()) {
112                Err(sys::Status::PendingException) => return Err(Throw::new()),
113                res => res.unwrap(),
114            }
115
116            arr.assume_init()
117        };
118
119        for (i, item) in iter.enumerate() {
120            let item = item.try_into_js(cx)?.to_local();
121            let Ok(i) = u32::try_from(i) else {
122                return cx.throw_error("Exceeded maximum length of an array");
123            };
124
125            unsafe {
126                match sys::set_element(env, arr, i, item) {
127                    Err(sys::Status::PendingException) => return Err(Throw::new()),
128                    res => res.unwrap(),
129                }
130            }
131        }
132
133        unsafe { Ok(Handle::new_internal(JsArray::from_local(cx.env(), arr))) }
134    }
135}
136
137/// Error when extracting an [`Array<T>`]
138#[derive(Debug)]
139pub enum ArrayError<E> {
140    /// Value was not a JavaScript array.
141    Array(TypeExpected<JsArray>),
142    /// An element failed to convert to `T::Item`.
143    Item(E),
144}
145
146impl<E> ArrayError<E> {
147    fn array() -> Self {
148        Self::Array(TypeExpected::<JsArray>::new())
149    }
150
151    fn item(err: E) -> Self {
152        Self::Item(err)
153    }
154}
155
156impl<E> fmt::Display for ArrayError<E>
157where
158    E: fmt::Display,
159{
160    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
161        match self {
162            ArrayError::Array(err) => write!(f, "{}", err),
163            ArrayError::Item(err) => write!(f, "{}", err),
164        }
165    }
166}
167
168impl<E> error::Error for ArrayError<E> where E: error::Error {}
169
170impl<'cx, E> TryIntoJs<'cx> for ArrayError<E>
171where
172    E: TryIntoJs<'cx>,
173{
174    type Value = JsValue;
175
176    fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> {
177        match self {
178            ArrayError::Array(err) => err.try_into_js(cx).map(|v| v.upcast()),
179            ArrayError::Item(err) => err.try_into_js(cx).map(|v| v.upcast()),
180        }
181    }
182}
183
184impl<'cx, E> private::Sealed for ArrayError<E> where E: TryIntoJs<'cx> {}