rqjs_ext/utils/
object.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3use std::collections::{BTreeMap, HashMap};
4
5use rquickjs::{
6    atom::PredefinedAtom, Array, ArrayBuffer, Coerced, Ctx, Exception, FromJs, Function, IntoAtom,
7    IntoJs, Object, Result, Symbol, TypedArray, Value,
8};
9
10use super::result::ResultExt;
11
12#[allow(dead_code)]
13pub fn array_to_hash_map<'js>(
14    ctx: &Ctx<'js>,
15    array: Array<'js>,
16) -> Result<HashMap<String, String>> {
17    let value = object_from_entries(ctx, array)?;
18    let value = value.into_value();
19    HashMap::from_js(ctx, value)
20}
21
22pub fn array_to_btree_map<'js>(
23    ctx: &Ctx<'js>,
24    array: Array<'js>,
25) -> Result<BTreeMap<String, Coerced<String>>> {
26    let value = object_from_entries(ctx, array)?;
27    let value = value.into_value();
28    BTreeMap::from_js(ctx, value)
29}
30
31pub fn object_from_entries<'js>(ctx: &Ctx<'js>, array: Array<'js>) -> Result<Object<'js>> {
32    let obj = Object::new(ctx.clone())?;
33    for value in array.into_iter().flatten() {
34        if let Some(entry) = value.as_array() {
35            if let Ok(key) = entry.get::<Value>(0) {
36                if let Ok(value) = entry.get::<Value>(1) {
37                    let _ = obj.set(key, value); //ignore result of failed
38                }
39            }
40        }
41    }
42    Ok(obj)
43}
44
45pub fn map_to_entries<'js, K, V, M>(ctx: &Ctx<'js>, map: M) -> Result<Array<'js>>
46where
47    M: IntoIterator<Item = (K, V)>,
48    K: IntoJs<'js>,
49    V: IntoJs<'js>,
50{
51    let array = Array::new(ctx.clone())?;
52    for (idx, (key, value)) in map.into_iter().enumerate() {
53        let entry = Array::new(ctx.clone())?;
54        entry.set(0, key)?;
55        entry.set(1, value)?;
56        array.set(idx, entry)?;
57    }
58
59    Ok(array)
60}
61
62pub fn get_start_end_indexes(
63    source_len: usize,
64    target_len: Option<usize>,
65    offset: usize,
66) -> (usize, usize) {
67    if offset > source_len {
68        return (0, 0);
69    }
70
71    let target_len = target_len.unwrap_or(source_len - offset);
72
73    if offset + target_len > source_len {
74        return (offset, source_len);
75    }
76
77    (offset, target_len + offset)
78}
79
80pub fn get_bytes_offset_length<'js>(
81    ctx: &Ctx<'js>,
82    value: Value<'js>,
83    offset: usize,
84    length: Option<usize>,
85) -> Result<Vec<u8>> {
86    if let Some(bytes) = get_string_bytes(&value, offset, length)? {
87        return Ok(bytes);
88    }
89    if let Some(bytes) = get_array_bytes(ctx, &value, offset, length)? {
90        return Ok(bytes);
91    }
92
93    if let Some(obj) = value.as_object() {
94        if let Some((array_buffer, source_length, source_offset)) = obj_to_array_buffer(obj)? {
95            let (start, end) = get_start_end_indexes(source_length, length, offset);
96            let bytes: &[u8] = array_buffer.as_ref();
97            return Ok(bytes[start + source_offset..end - source_offset].to_vec());
98        }
99    }
100
101    if let Some(bytes) = get_coerced_string_bytes(&value, offset, length) {
102        return Ok(bytes);
103    }
104
105    Err(Exception::throw_message(
106        ctx,
107        "value must be typed DataView, Buffer, ArrayBuffer, Uint8Array or interpretable as string",
108    ))
109}
110
111pub fn get_array_bytes<'js>(
112    ctx: &Ctx<'js>,
113    value: &Value<'js>,
114    offset: usize,
115    length: Option<usize>,
116) -> Result<Option<Vec<u8>>> {
117    if value.is_array() {
118        let array = value.as_array().unwrap();
119        let (start, end) = get_start_end_indexes(array.len(), length, offset);
120        let size = end - start;
121        let mut bytes: Vec<u8> = Vec::with_capacity(size);
122
123        for val in array.iter::<u8>().skip(start).take(size) {
124            let val: u8 = val.or_throw_msg(ctx, "array value is not u8")?;
125            bytes.push(val);
126        }
127
128        return Ok(Some(bytes));
129    }
130    Ok(None)
131}
132
133pub fn get_coerced_string_bytes(
134    value: &Value<'_>,
135    offset: usize,
136    length: Option<usize>,
137) -> Option<Vec<u8>> {
138    if let Ok(val) = value.get::<Coerced<String>>() {
139        let string = val.to_string();
140        return Some(bytes_from_js_string(string, offset, length));
141    };
142    None
143}
144
145#[inline]
146pub fn get_string_bytes(
147    value: &Value<'_>,
148    offset: usize,
149    length: Option<usize>,
150) -> Result<Option<Vec<u8>>> {
151    if let Some(val) = value.as_string() {
152        let string = val.to_string()?;
153        return Ok(Some(bytes_from_js_string(string, offset, length)));
154    }
155    Ok(None)
156}
157
158fn bytes_from_js_string(string: String, offset: usize, length: Option<usize>) -> Vec<u8> {
159    let (start, end) = get_start_end_indexes(string.len(), length, offset);
160    string.as_bytes()[start..end].to_vec()
161}
162
163pub fn obj_to_array_buffer<'js>(
164    obj: &Object<'js>,
165) -> Result<Option<(ArrayBuffer<'js>, usize, usize)>> {
166    //most common
167    if let Ok(typed_array) = TypedArray::<u8>::from_object(obj.clone()) {
168        let byte_length = typed_array.len();
169        let offset: usize = typed_array.get("byteOffset")?;
170        return Ok(Some((typed_array.arraybuffer()?, byte_length, offset)));
171    }
172    //second most common
173    if let Some(array_buffer) = ArrayBuffer::from_object(obj.clone()) {
174        let byte_length = array_buffer.len();
175        return Ok(Some((array_buffer, byte_length, 0)));
176    }
177
178    if let Ok(typed_array) = TypedArray::<i8>::from_object(obj.clone()) {
179        let byte_length = typed_array.len();
180        let offset: usize = typed_array.get("byteOffset")?;
181        return Ok(Some((typed_array.arraybuffer()?, byte_length, offset)));
182    }
183
184    if let Ok(typed_array) = TypedArray::<u16>::from_object(obj.clone()) {
185        let byte_length = typed_array.len() * 2;
186        let offset: usize = typed_array.get("byteOffset")?;
187        return Ok(Some((typed_array.arraybuffer()?, byte_length, offset)));
188    }
189
190    if let Ok(typed_array) = TypedArray::<i16>::from_object(obj.clone()) {
191        let byte_length = typed_array.len() * 2;
192        let offset: usize = typed_array.get("byteOffset")?;
193        return Ok(Some((typed_array.arraybuffer()?, byte_length, offset)));
194    }
195
196    if let Ok(typed_array) = TypedArray::<u32>::from_object(obj.clone()) {
197        let byte_length = typed_array.len() * 4;
198        let offset: usize = typed_array.get("byteOffset")?;
199        return Ok(Some((typed_array.arraybuffer()?, byte_length, offset)));
200    }
201
202    if let Ok(typed_array) = TypedArray::<i32>::from_object(obj.clone()) {
203        let byte_length = typed_array.len() * 4;
204        let offset: usize = typed_array.get("byteOffset")?;
205        return Ok(Some((typed_array.arraybuffer()?, byte_length, offset)));
206    }
207
208    if let Ok(typed_array) = TypedArray::<u64>::from_object(obj.clone()) {
209        let byte_length = typed_array.len() * 8;
210        let offset: usize = typed_array.get("byteOffset")?;
211        return Ok(Some((typed_array.arraybuffer()?, byte_length, offset)));
212    }
213
214    if let Ok(typed_array) = TypedArray::<i64>::from_object(obj.clone()) {
215        let byte_length = typed_array.len() * 8;
216        let offset: usize = typed_array.get("byteOffset")?;
217        return Ok(Some((typed_array.arraybuffer()?, byte_length, offset)));
218    }
219
220    if let Ok(typed_array) = TypedArray::<f32>::from_object(obj.clone()) {
221        let byte_length = typed_array.len() * 4;
222        let offset: usize = typed_array.get("byteOffset")?;
223        return Ok(Some((typed_array.arraybuffer()?, byte_length, offset)));
224    }
225
226    if let Ok(typed_array) = TypedArray::<f64>::from_object(obj.clone()) {
227        let byte_length = typed_array.len() * 8;
228        let offset: usize = typed_array.get("byteOffset")?;
229        return Ok(Some((typed_array.arraybuffer()?, byte_length, offset)));
230    }
231
232    if let Ok(array_buffer) = obj.get::<_, ArrayBuffer>("buffer") {
233        let length = array_buffer.len();
234        return Ok(Some((array_buffer, length, 0)));
235    }
236
237    Ok(None)
238}
239
240pub fn get_array_buffer_bytes(
241    array_buffer: ArrayBuffer<'_>,
242    start: usize,
243    end_end: usize,
244) -> Vec<u8> {
245    let bytes: &[u8] = array_buffer.as_ref();
246    bytes[start..end_end].to_vec()
247}
248
249pub fn get_bytes<'js>(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Vec<u8>> {
250    get_bytes_offset_length(ctx, value, 0, None)
251}
252
253pub fn bytes_to_typed_array<'js>(ctx: Ctx<'js>, bytes: &[u8]) -> Result<Value<'js>> {
254    TypedArray::<u8>::new(ctx.clone(), bytes).into_js(&ctx)
255}
256
257pub trait ObjectExt<'js> {
258    fn get_optional<K: IntoAtom<'js> + Clone, V: FromJs<'js>>(&self, k: K) -> Result<Option<V>>;
259}
260
261impl<'js> ObjectExt<'js> for Object<'js> {
262    fn get_optional<K: IntoAtom<'js> + Clone, V: FromJs<'js> + Sized>(
263        &self,
264        k: K,
265    ) -> Result<Option<V>> {
266        self.get::<K, Option<V>>(k)
267    }
268}
269
270impl<'js> ObjectExt<'js> for Value<'js> {
271    fn get_optional<K: IntoAtom<'js> + Clone, V: FromJs<'js>>(&self, k: K) -> Result<Option<V>> {
272        if let Some(obj) = self.as_object() {
273            return obj.get_optional(k);
274        }
275        Ok(None)
276    }
277}
278
279pub trait CreateSymbol<'js> {
280    fn for_description(globals: &Object<'js>, description: &'static str) -> Result<Symbol<'js>>;
281}
282
283impl<'js> CreateSymbol<'js> for Symbol<'js> {
284    fn for_description(globals: &Object<'js>, description: &'static str) -> Result<Symbol<'js>> {
285        let symbol_function: Function = globals.get(PredefinedAtom::Symbol)?;
286        let for_function: Function = symbol_function.get(PredefinedAtom::For)?;
287        for_function.call((description,))
288    }
289}