Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move UncheckedIter to gloo-utils #217

Merged
merged 2 commits into from
Apr 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 5 additions & 21 deletions crates/net/src/http/headers.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use gloo_utils::iter::UncheckedIter;
use js_sys::{Array, Map};
use std::fmt;
use wasm_bindgen::{JsCast, JsValue, UnwrapThrowExt};
use wasm_bindgen::{JsCast, UnwrapThrowExt};

// I experimented with using `js_sys::Object` for the headers, since this object is marked
// experimental in MDN. However it's in the fetch spec, and it's necessary for appending headers.
Expand Down Expand Up @@ -68,7 +69,7 @@ impl Headers {
// and everything works. Is there a better way? Should there be a `MapLike` or
// `MapIterator` type in `js_sys`?
let fake_map: &Map = self.raw.unchecked_ref();
UncheckedIter(fake_map.entries()).map(|entry| {
UncheckedIter::from(fake_map.entries()).map(|entry| {
let entry: Array = entry.unchecked_into();
let key = entry.get(0);
let value = entry.get(1);
Expand All @@ -82,13 +83,13 @@ impl Headers {
/// Iterate over the names of the headers.
pub fn keys(&self) -> impl Iterator<Item = String> {
let fake_map: &Map = self.raw.unchecked_ref();
UncheckedIter(fake_map.keys()).map(|key| key.as_string().unwrap_throw())
UncheckedIter::from(fake_map.keys()).map(|key| key.as_string().unwrap_throw())
}

/// Iterate over the values of the headers.
pub fn values(&self) -> impl Iterator<Item = String> {
let fake_map: &Map = self.raw.unchecked_ref();
UncheckedIter(fake_map.values()).map(|v| v.as_string().unwrap_throw())
UncheckedIter::from(fake_map.values()).map(|v| v.as_string().unwrap_throw())
}
}

Expand All @@ -101,20 +102,3 @@ impl fmt::Debug for Headers {
dbg.finish()
}
}

struct UncheckedIter(js_sys::Iterator);

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())
}
}
}
3 changes: 3 additions & 0 deletions crates/utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ features = [
"HtmlHeadElement",
"Element",
]

[dev-dependencies]
wasm-bindgen-test = "0.3"
97 changes: 97 additions & 0 deletions crates/utils/src/iter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,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);
}
}
1 change: 1 addition & 0 deletions crates/utils/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod errors;
pub mod iter;
use wasm_bindgen::UnwrapThrowExt;

/// Convenience function to avoid repeating expect logic.
Expand Down