1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
use wasm_bindgen::{JsValue, UnwrapThrowExt};

/// A wrapper around JS Iterator so it can be consumed from Rust.
///
/// This type implements [`Iterator`] trait and will keep yielding [`JsValue`]
/// until the underlying [`js_sys::Iterator`] is exuasted.
///
/// This type is called `UncheckedIter` because it does no checking for
/// the underlying type of the [`js_sys::Iterator`] and yields [`JsValue`]s.
///
/// # Example
///
/// ```rust
/// use gloo_utils::iter::UncheckedIter;
/// use wasm_bindgen::{JsCast, JsValue, UnwrapThrowExt};
///
/// # fn no_run() {
/// let map = js_sys::Map::new();
/// map.set(&JsValue::from("one"), &JsValue::from(1_f64));
///
/// let mut iter = UncheckedIter::from(map.entries()).map(|js_value| {
///     let array: js_sys::Array = js_value.unchecked_into();
///     (
///         array.get(0).as_string().unwrap_throw(),
///         array.get(1).as_f64().unwrap_throw(),
///     )
/// });
///
/// assert_eq!(iter.next(), Some((String::from("one"), 1_f64)));
/// assert_eq!(iter.next(), None);
/// # }
/// ```
pub struct UncheckedIter(js_sys::Iterator);

impl UncheckedIter {
    /// Obtain the raw [`js_sys::Iterator`]
    pub fn into_raw(self) -> js_sys::Iterator {
        self.0
    }
}

impl From<js_sys::Iterator> for UncheckedIter {
    fn from(iter: js_sys::Iterator) -> Self {
        Self(iter)
    }
}

impl Iterator for UncheckedIter {
    type Item = JsValue;

    fn next(&mut self) -> Option<Self::Item> {
        // we don't check for errors. Only use this type on things we know conform to the iterator
        // interface.
        let next = self.0.next().unwrap_throw();
        if next.done() {
            None
        } else {
            Some(next.value())
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use wasm_bindgen_test::*;

    wasm_bindgen_test_configure!(run_in_browser);

    #[wasm_bindgen_test]
    fn it_works() {
        let map = js_sys::Map::new();
        macro_rules! map_set {
            ($key:expr => $value:expr) => {
                map.set(&JsValue::from($key), &JsValue::from($value));
            };
        }

        map_set!("one" => 1_f64);
        map_set!("two" => 2_f64);
        map_set!("three" => 3_f64);

        let mut iter = UncheckedIter::from(map.entries()).map(|js_value| {
            let array = js_sys::Array::from(&js_value);
            let array = array.to_vec();
            (
                array[0].as_string().expect_throw("not string"),
                array[1].as_f64().expect_throw("not f64"),
            )
        });

        assert_eq!(iter.next(), Some((String::from("one"), 1_f64)));
        assert_eq!(iter.next(), Some((String::from("two"), 2_f64)));
        assert_eq!(iter.next(), Some((String::from("three"), 3_f64)));
        assert_eq!(iter.next(), None);
    }
}