From c6bae2769c234bb0d212d33f3c619994bf11371a Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 31 Oct 2023 19:24:36 +0100 Subject: [PATCH 1/9] Add ReadableStream::from() --- src/readable/mod.rs | 19 +++++++++++++++++++ src/readable/sys.rs | 3 +++ 2 files changed, 22 insertions(+) diff --git a/src/readable/mod.rs b/src/readable/mod.rs index c47d969..f4f1d41 100644 --- a/src/readable/mod.rs +++ b/src/readable/mod.rs @@ -2,6 +2,7 @@ //! [readable streams](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream). use futures_util::io::AsyncRead; use futures_util::Stream; +use js_sys::Object; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; @@ -98,6 +99,24 @@ impl ReadableStream { Self { raw } } + /// Creates a new `ReadableStream` wrapping the provided [iterable] or [async iterable]. + /// + /// This can be used to adapt various kinds of objects into a readable stream, + /// such as an [array], an [async generator] or a [Node.js readable stream][Readable]. + /// + /// [iterable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterable_protocol + /// [async iterable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_async_iterator_and_async_iterable_protocols + /// [array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array + /// [async generator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator + /// [Readable]: https://nodejs.org/api/stream.html#class-streamreadable + // TODO Non-panicking variant? + pub fn from(async_iterable: Object) -> Self { + let raw = sys::ReadableStreamExt::from_async_iterable(&async_iterable) + .unwrap_throw() + .unchecked_into(); + Self { raw } + } + /// Acquires a reference to the underlying [JavaScript stream](sys::ReadableStream). #[inline] pub fn as_raw(&self) -> &sys::ReadableStream { diff --git a/src/readable/sys.rs b/src/readable/sys.rs index d1000c2..c57c29e 100644 --- a/src/readable/sys.rs +++ b/src/readable/sys.rs @@ -47,6 +47,9 @@ extern "C" { #[wasm_bindgen(method, catch, js_class = ReadableStream, js_name = tee)] pub(crate) fn try_tee(this: &ReadableStreamExt) -> Result; + + #[wasm_bindgen(catch, static_method_of = ReadableStreamExt, js_class = ReadableStream, js_name = from)] + pub(crate) fn from_async_iterable(async_iterable: &Object) -> Result; } #[wasm_bindgen] From 00f4667542fea51f8e2be38c0193892ccebd1b0e Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 31 Oct 2023 19:25:42 +0100 Subject: [PATCH 2/9] Use from_raw() internally --- src/readable/mod.rs | 6 +++--- src/writable/mod.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/readable/mod.rs b/src/readable/mod.rs index f4f1d41..e064244 100644 --- a/src/readable/mod.rs +++ b/src/readable/mod.rs @@ -73,7 +73,7 @@ impl ReadableStream { let strategy = QueuingStrategy::new(0.0); let raw = sys::ReadableStreamExt::new_with_into_underlying_source(source, strategy) .unchecked_into(); - Self { raw } + Self::from_raw(raw) } /// Creates a new `ReadableStream` from an [`AsyncRead`]. @@ -96,7 +96,7 @@ impl ReadableStream { let raw = sys::ReadableStreamExt::new_with_into_underlying_byte_source(source) .expect_throw("readable byte streams not supported") .unchecked_into(); - Self { raw } + Self::from_raw(raw) } /// Creates a new `ReadableStream` wrapping the provided [iterable] or [async iterable]. @@ -114,7 +114,7 @@ impl ReadableStream { let raw = sys::ReadableStreamExt::from_async_iterable(&async_iterable) .unwrap_throw() .unchecked_into(); - Self { raw } + Self::from_raw(raw) } /// Acquires a reference to the underlying [JavaScript stream](sys::ReadableStream). diff --git a/src/writable/mod.rs b/src/writable/mod.rs index 08ef9b5..494cf88 100644 --- a/src/writable/mod.rs +++ b/src/writable/mod.rs @@ -55,7 +55,7 @@ impl WritableStream { // Use the default queuing strategy (with a HWM of 1 chunk). // We shouldn't set HWM to 0, since that would break piping to the writable stream. let raw = sys::WritableStreamExt::new_with_into_underlying_sink(sink).unchecked_into(); - WritableStream { raw } + Self::from_raw(raw) } /// Acquires a reference to the underlying [JavaScript stream](sys::WritableStream). From 97fc45f8f39439570981f6b1ef17b88cbf2e8abf Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 31 Oct 2023 19:28:09 +0100 Subject: [PATCH 3/9] Document panics --- src/readable/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/readable/mod.rs b/src/readable/mod.rs index e064244..2afe280 100644 --- a/src/readable/mod.rs +++ b/src/readable/mod.rs @@ -104,6 +104,9 @@ impl ReadableStream { /// This can be used to adapt various kinds of objects into a readable stream, /// such as an [array], an [async generator] or a [Node.js readable stream][Readable]. /// + /// **Panics** if `ReadableStream.from()` is not supported by the browser, + /// or if the given object is not a valid iterable or async iterable. + /// /// [iterable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterable_protocol /// [async iterable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_async_iterator_and_async_iterable_protocols /// [array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array From b8eeb06ec4a935378bd56ecc0ac798737d2b0b2e Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 31 Oct 2023 19:34:05 +0100 Subject: [PATCH 4/9] Add test --- tests/tests/readable_stream.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/tests/readable_stream.rs b/tests/tests/readable_stream.rs index c353178..30311b2 100644 --- a/tests/tests/readable_stream.rs +++ b/tests/tests/readable_stream.rs @@ -298,3 +298,17 @@ async fn test_readable_stream_into_stream_then_into_async_read() { assert_eq!(async_read.read(&mut buf).await.unwrap(), 0); assert_eq!(&buf, &[4, 5, 6]); } + +#[wasm_bindgen_test] +async fn test_readable_stream_from_js_array() { + let js_array = + js_sys::Array::from_iter([JsValue::from_str("Hello"), JsValue::from_str("world!")]); + let mut readable = ReadableStream::from(js_array.unchecked_into()); + assert!(!readable.is_locked()); + + let mut reader = readable.get_reader(); + assert_eq!(reader.read().await.unwrap(), Some(JsValue::from("Hello"))); + assert_eq!(reader.read().await.unwrap(), Some(JsValue::from("world!"))); + assert_eq!(reader.read().await.unwrap(), None); + reader.closed().await.unwrap(); +} From 1699580f6af563c1d33929d08c7e4f6714c7a509 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 31 Oct 2023 19:48:27 +0100 Subject: [PATCH 5/9] Add ReadableStream::try_from() --- src/readable/mod.rs | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/readable/mod.rs b/src/readable/mod.rs index 2afe280..1e5f5c4 100644 --- a/src/readable/mod.rs +++ b/src/readable/mod.rs @@ -3,8 +3,8 @@ use futures_util::io::AsyncRead; use futures_util::Stream; use js_sys::Object; -use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; +use wasm_bindgen::prelude::*; pub use byob_reader::ReadableStreamBYOBReader; pub use default_reader::ReadableStreamDefaultReader; @@ -106,18 +106,34 @@ impl ReadableStream { /// /// **Panics** if `ReadableStream.from()` is not supported by the browser, /// or if the given object is not a valid iterable or async iterable. + /// For a non-panicking variant, use [`try_from`](Self::try_from). /// /// [iterable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterable_protocol /// [async iterable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_async_iterator_and_async_iterable_protocols /// [array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array /// [async generator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator /// [Readable]: https://nodejs.org/api/stream.html#class-streamreadable - // TODO Non-panicking variant? pub fn from(async_iterable: Object) -> Self { - let raw = sys::ReadableStreamExt::from_async_iterable(&async_iterable) - .unwrap_throw() - .unchecked_into(); - Self::from_raw(raw) + Self::try_from(async_iterable).unwrap_throw() + } + + /// Try to create a new `ReadableStream` wrapping the provided [iterable] or [async iterable]. + /// + /// This can be used to adapt various kinds of objects into a readable stream, + /// such as an [array], an [async generator] or a [Node.js readable stream][Readable]. + /// + /// If `ReadableStream.from()` is not supported by the browser, + /// or if the given object is not a valid iterable or async iterable, + /// then this returns an error. + /// + /// [iterable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterable_protocol + /// [async iterable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_async_iterator_and_async_iterable_protocols + /// [array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array + /// [async generator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator + /// [Readable]: https://nodejs.org/api/stream.html#class-streamreadable + pub fn try_from(async_iterable: Object) -> Result { + let raw = sys::ReadableStreamExt::from_async_iterable(&async_iterable)?.unchecked_into(); + Ok(Self::from_raw(raw)) } /// Acquires a reference to the underlying [JavaScript stream](sys::ReadableStream). From 1ac0e497d2c8ca7aa0f3523c0ba1f946ea45be80 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 31 Oct 2023 19:55:20 +0100 Subject: [PATCH 6/9] Ignore test if browser doesn't support ReadableStream.from() --- tests/tests/readable_stream.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/tests/readable_stream.rs b/tests/tests/readable_stream.rs index 30311b2..fec2076 100644 --- a/tests/tests/readable_stream.rs +++ b/tests/tests/readable_stream.rs @@ -303,7 +303,18 @@ async fn test_readable_stream_into_stream_then_into_async_read() { async fn test_readable_stream_from_js_array() { let js_array = js_sys::Array::from_iter([JsValue::from_str("Hello"), JsValue::from_str("world!")]); - let mut readable = ReadableStream::from(js_array.unchecked_into()); + let mut readable = match ReadableStream::try_from(js_array.unchecked_into()) { + Ok(readable) => readable, + Err(err) => { + // ReadableStream.from() is not yet supported in all browsers. + assert_eq!(err.name(), "TypeError"); + assert_eq!( + err.message().as_string().unwrap(), + "ReadableStream.from is not a function" + ); + return; + } + }; assert!(!readable.is_locked()); let mut reader = readable.get_reader(); From 9fa43d36530ca4eb0064d8f0fac7bdd5120ba2f9 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 31 Oct 2023 20:00:03 +0100 Subject: [PATCH 7/9] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ad46e1..5174c10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +* Added `ReadableStream::from(async_iterable)` and `try_from(async_iterable)`. ([#23](https://github.com/MattiasBuelens/wasm-streams/pull/23)) * Stop calling `byobRequest.respond(0)` on cancel ([#16](https://github.com/MattiasBuelens/wasm-streams/pull/16)) * ⚠ **Breaking change:** The system modules (`readable::sys`, `writable::sys` and `transform::sys`) now re-export directly from [the `web-sys` crate](https://docs.rs/web-sys/latest/web_sys/). This should make it easier to use `from_raw()`, `as_raw()` and `into_raw()`. ([#22](https://github.com/MattiasBuelens/wasm-streams/pull/22/)) From c42fe2c727ab25f467b36eeceb109599e610148a Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 31 Oct 2023 20:00:13 +0100 Subject: [PATCH 8/9] Tiny fix --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5174c10..6d22d3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ * Added `ReadableStream::from(async_iterable)` and `try_from(async_iterable)`. ([#23](https://github.com/MattiasBuelens/wasm-streams/pull/23)) * Stop calling `byobRequest.respond(0)` on cancel ([#16](https://github.com/MattiasBuelens/wasm-streams/pull/16)) -* ⚠ **Breaking change:** The system modules (`readable::sys`, `writable::sys` and `transform::sys`) now re-export directly from [the `web-sys` crate](https://docs.rs/web-sys/latest/web_sys/). This should make it easier to use `from_raw()`, `as_raw()` and `into_raw()`. ([#22](https://github.com/MattiasBuelens/wasm-streams/pull/22/)) +* ⚠ **Breaking change:** The system modules (`readable::sys`, `writable::sys` and `transform::sys`) now re-export directly from [the `web-sys` crate](https://docs.rs/web-sys/latest/web_sys/). This should make it easier to use `from_raw()`, `as_raw()` and `into_raw()`. ([#22](https://github.com/MattiasBuelens/wasm-streams/pull/22)) ## v0.3.0 (2022-10-16) From 27ec304db59874764e418db81a62f85ae20244fd Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 31 Oct 2023 20:01:46 +0100 Subject: [PATCH 9/9] Fix formatting --- src/readable/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/readable/mod.rs b/src/readable/mod.rs index 1e5f5c4..7e0491b 100644 --- a/src/readable/mod.rs +++ b/src/readable/mod.rs @@ -3,8 +3,8 @@ use futures_util::io::AsyncRead; use futures_util::Stream; use js_sys::Object; -use wasm_bindgen::JsCast; use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; pub use byob_reader::ReadableStreamBYOBReader; pub use default_reader::ReadableStreamDefaultReader;