wasm_streams/writable/
mod.rs

1//! Bindings and conversions for
2//! [writable streams](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream).
3
4use futures_util::Sink;
5use wasm_bindgen::prelude::*;
6
7pub use default_writer::WritableStreamDefaultWriter;
8pub use into_async_write::IntoAsyncWrite;
9pub use into_sink::IntoSink;
10use into_underlying_sink::IntoUnderlyingSink;
11
12use crate::util::promise_to_void_future;
13
14mod default_writer;
15mod into_async_write;
16mod into_sink;
17mod into_underlying_sink;
18pub mod sys;
19
20/// A [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream).
21///
22/// `WritableStream`s can be created from a [raw JavaScript stream](sys::WritableStream) with
23/// [`from_raw`](Self::from_raw), or from a Rust [`Sink`] with [`from_sink`](Self::from_sink).
24///
25/// They can be converted into a [raw JavaScript stream](sys::WritableStream) with
26/// [`into_raw`](Self::into_raw), or into a Rust [`Sink`] with [`into_sink`](Self::into_sink).
27///
28/// [`Sink`]: https://docs.rs/futures/0.3.18/futures/sink/trait.Sink.html
29#[derive(Debug)]
30pub struct WritableStream {
31    raw: sys::WritableStream,
32}
33
34impl WritableStream {
35    /// Creates a new `WritableStream` from a [JavaScript stream](sys::WritableStream).
36    #[inline]
37    pub fn from_raw(raw: sys::WritableStream) -> Self {
38        Self { raw }
39    }
40
41    /// Creates a new `WritableStream` from a [`Sink`].
42    ///
43    /// Items and errors must be represented as raw [`JsValue`](JsValue)s.
44    /// Use [`with`] and/or [`sink_map_err`] to convert a sink's items to a `JsValue`
45    /// before passing it to this function.
46    ///
47    /// [`Sink`]: https://docs.rs/futures/0.3.18/futures/sink/trait.Sink.html
48    /// [`with`]: https://docs.rs/futures/0.3.18/futures/sink/trait.SinkExt.html#method.with
49    /// [`sink_map_err`]: https://docs.rs/futures/0.3.18/futures/sink/trait.SinkExt.html#method.sink_map_err
50    pub fn from_sink<Si>(sink: Si) -> Self
51    where
52        Si: Sink<JsValue, Error = JsValue> + 'static,
53    {
54        let sink = IntoUnderlyingSink::new(Box::new(sink));
55        // Use the default queuing strategy (with a HWM of 1 chunk).
56        // We shouldn't set HWM to 0, since that would break piping to the writable stream.
57        let raw = sys::WritableStream::new_with_sink(sink);
58        WritableStream { raw }
59    }
60
61    /// Acquires a reference to the underlying [JavaScript stream](sys::WritableStream).
62    #[inline]
63    pub fn as_raw(&self) -> &sys::WritableStream {
64        &self.raw
65    }
66
67    /// Consumes this `WritableStream`, returning the underlying [JavaScript stream](sys::WritableStream).
68    #[inline]
69    pub fn into_raw(self) -> sys::WritableStream {
70        self.raw
71    }
72
73    /// Returns `true` if the stream is [locked to a writer](https://streams.spec.whatwg.org/#lock).
74    #[inline]
75    pub fn is_locked(&self) -> bool {
76        self.as_raw().is_locked()
77    }
78
79    /// [Aborts](https://streams.spec.whatwg.org/#abort-a-writable-stream) the stream,
80    /// signaling that the producer can no longer successfully write to the stream
81    /// and it is to be immediately moved to an errored state, with any queued-up writes discarded.
82    ///
83    /// If the stream is currently locked to a writer, then this returns an error.
84    pub async fn abort(&mut self) -> Result<(), JsValue> {
85        promise_to_void_future(self.as_raw().abort()).await
86    }
87
88    /// [Aborts](https://streams.spec.whatwg.org/#abort-a-writable-stream) the stream with the
89    /// given `reason`, signaling that the producer can no longer successfully write to the stream
90    /// and it is to be immediately moved to an errored state, with any queued-up writes discarded.
91    ///
92    /// If the stream is currently locked to a writer, then this returns an error.
93    pub async fn abort_with_reason(&mut self, reason: &JsValue) -> Result<(), JsValue> {
94        promise_to_void_future(self.as_raw().abort_with_reason(reason)).await
95    }
96
97    /// Creates a [writer](WritableStreamDefaultWriter) and
98    /// [locks](https://streams.spec.whatwg.org/#lock) the stream to the new writer.
99    ///
100    /// While the stream is locked, no other writer can be acquired until this one is released.
101    ///
102    /// **Panics** if the stream is already locked to a writer. For a non-panicking variant,
103    /// use [`try_get_writer`](Self::try_get_writer).
104    #[inline]
105    pub fn get_writer(&mut self) -> WritableStreamDefaultWriter {
106        self.try_get_writer()
107            .expect_throw("already locked to a writer")
108    }
109
110    /// Try to create a [writer](WritableStreamDefaultWriter) and
111    /// [lock](https://streams.spec.whatwg.org/#lock) the stream to the new writer.
112    ///
113    /// While the stream is locked, no other writer can be acquired until this one is released.
114    ///
115    /// If the stream is already locked to a writer, then this returns an error.
116    pub fn try_get_writer(&mut self) -> Result<WritableStreamDefaultWriter, js_sys::Error> {
117        WritableStreamDefaultWriter::new(self)
118    }
119
120    /// Converts this `WritableStream` into a [`Sink`].
121    ///
122    /// Items and errors are represented by their raw [`JsValue`](JsValue).
123    /// Use [`with`] and/or [`sink_map_err`] on the returned stream to convert them to a more
124    /// appropriate type.
125    ///
126    /// **Panics** if the stream is already locked to a writer. For a non-panicking variant,
127    /// use [`try_into_sink`](Self::try_into_sink).
128    ///
129    /// [`Sink`]: https://docs.rs/futures/0.3.18/futures/sink/trait.Sink.html
130    /// [`with`]: https://docs.rs/futures/0.3.18/futures/sink/trait.SinkExt.html#method.with
131    /// [`sink_map_err`]: https://docs.rs/futures/0.3.18/futures/sink/trait.SinkExt.html#method.sink_map_err
132    #[inline]
133    pub fn into_sink(self) -> IntoSink<'static> {
134        self.try_into_sink()
135            .expect_throw("already locked to a writer")
136    }
137
138    /// Try to convert this `WritableStream` into a [`Sink`].
139    ///
140    /// Items and errors are represented by their raw [`JsValue`](JsValue).
141    /// Use [`with`] and/or [`sink_map_err`] on the returned stream to convert them to a more
142    /// appropriate type.
143    ///
144    /// If the stream is already locked to a writer, then this returns an error
145    /// along with the original `WritableStream`.
146    ///
147    /// [`Sink`]: https://docs.rs/futures/0.3.18/futures/sink/trait.Sink.html
148    /// [`with`]: https://docs.rs/futures/0.3.18/futures/sink/trait.SinkExt.html#method.with
149    /// [`sink_map_err`]: https://docs.rs/futures/0.3.18/futures/sink/trait.SinkExt.html#method.sink_map_err
150    pub fn try_into_sink(mut self) -> Result<IntoSink<'static>, (js_sys::Error, Self)> {
151        let writer = WritableStreamDefaultWriter::new(&mut self).map_err(|err| (err, self))?;
152        Ok(writer.into_sink())
153    }
154
155    /// Converts this `WritableStream` into an [`AsyncWrite`].
156    ///
157    /// The writable stream must accept [`Uint8Array`](js_sys::Uint8Array) chunks.
158    ///
159    /// **Panics** if the stream is already locked to a writer. For a non-panicking variant,
160    /// use [`try_into_async_write`](Self::try_into_async_write).
161    ///
162    /// [`AsyncWrite`]: https://docs.rs/futures/0.3.18/futures/io/trait.AsyncWrite.html
163    pub fn into_async_write(self) -> IntoAsyncWrite<'static> {
164        self.try_into_async_write()
165            .expect_throw("already locked to a writer")
166    }
167
168    /// Try to convert this `WritableStream` into an [`AsyncWrite`].
169    ///
170    /// The writable stream must accept [`Uint8Array`](js_sys::Uint8Array) chunks.
171    ///
172    /// If the stream is already locked to a writer, then this returns an error
173    /// along with the original `WritableStream`.
174    ///
175    /// [`AsyncWrite`]: https://docs.rs/futures/0.3.18/futures/io/trait.AsyncWrite.html
176    pub fn try_into_async_write(self) -> Result<IntoAsyncWrite<'static>, (js_sys::Error, Self)> {
177        Ok(IntoAsyncWrite::new(self.try_into_sink()?))
178    }
179}
180
181impl<Si> From<Si> for WritableStream
182where
183    Si: Sink<JsValue, Error = JsValue> + 'static,
184{
185    /// Equivalent to [`from_sink`](Self::from_sink).
186    #[inline]
187    fn from(sink: Si) -> Self {
188        Self::from_sink(sink)
189    }
190}