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}