google_cloud_gax/streaming.rs
1// Copyright 2026 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Defines types and handles for streaming RPCs.
16//!
17//! In streaming RPCs, the client and server can stream messages to each other
18//! asynchronously. This module provides [`RequestSender`] to send outbound request
19//! messages and [`ResponseStream`] to receive inbound response messages.
20//!
21//! Client libraries use these types to manage streaming communication without
22//! exposing raw gRPC transport or Protobuf wire models in the public API.
23//!
24//! Connection establishment begins immediately when the streaming RPC is initiated.
25//! In bidirectional streaming, the server may require initial request messages sent via
26//! [`RequestSender`] before returning response headers; completing this handshake is awaited
27//! on the first call to [`ResponseStream::next()`].
28//!
29//! # Examples
30//!
31//! Sending requests and receiving responses in a bidirectional streaming RPC:
32//!
33//! ```
34//! # use google_cloud_gax::streaming::{RequestSender, ResponseStream};
35//! async fn interact_with_bidi_stream(
36//! sender: RequestSender<String>,
37//! mut stream: ResponseStream<String>,
38//! ) -> anyhow::Result<()> {
39//! // Send request messages to the server:
40//! sender.send("hello".to_string()).await?;
41//!
42//! // Close the outbound request stream when done sending:
43//! drop(sender);
44//!
45//! // Receive response messages from the server:
46//! while let Some(response) = stream.next().await {
47//! let response = response?;
48//! println!("Received response: {response:?}");
49//! }
50//! Ok(())
51//! }
52//! ```
53//!
54//! Receiving response messages in a server-streaming RPC:
55//!
56//! ```
57//! # use google_cloud_gax::streaming::ResponseStream;
58//! # use google_cloud_gax::Result;
59//! async fn interact_with_server_stream(mut stream: ResponseStream<String>) -> Result<()> {
60//! // Receive response messages from the server:
61//! while let Some(response) = stream.next().await {
62//! let response = response?;
63//! println!("Received response: {response:?}");
64//! }
65//! Ok(())
66//! }
67//! ```
68
69use std::future::Future;
70use std::pin::Pin;
71use std::sync::Arc;
72use tokio::sync::mpsc;
73type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
74
75/// An error returned when sending a request over a stream fails.
76///
77/// Returned by [`RequestSender::send`].
78///
79/// # Examples
80///
81/// ```
82/// # use google_cloud_gax::streaming::SendError;
83/// fn handle_error(err: SendError) {
84/// match err {
85/// SendError::StreamClosed => {
86/// println!("Stream closed; check ResponseStream for server status");
87/// }
88/// SendError::Serialization(e) => {
89/// println!("Failed to serialize request: {e:?}");
90/// }
91/// _ => println!("Other send error"),
92/// }
93/// }
94/// ```
95#[derive(Debug, thiserror::Error)]
96#[non_exhaustive]
97pub enum SendError {
98 /// The stream was closed by the server or receiver.
99 ///
100 /// In bidirectional streaming, the underlying server status error (e.g., `InvalidArgument`,
101 /// `PermissionDenied`) is returned by [`ResponseStream::next`].
102 #[error("cannot send request: stream is closed; inspect ResponseStream for details")]
103 StreamClosed,
104
105 /// Serialization / proto conversion of the request failed.
106 #[error("cannot serialize the request: {0}")]
107 Serialization(#[source] BoxError),
108}
109
110/// A type-erased asynchronous function that sends a request item over a stream.
111///
112/// This closure takes an owned request item and returns a boxed, pinned future
113/// producing a `Result<(), SendError>`. It allows [`RequestSender`] to perform
114/// pre-send transformations (such as converting high-level domain models to
115/// low-level Protobuf wire types) without exposing the underlying transport or
116/// wire types in the public API.
117type SenderFn<Req> =
118 dyn Fn(Req) -> Pin<Box<dyn Future<Output = Result<(), SendError>> + Send>> + Send + Sync;
119
120/// A handle for sending outbound request items over a streaming RPC.
121///
122/// Typically, you receive a `RequestSender` as the result of initiating a streaming RPC.
123/// Outbound messages are buffered and sent asynchronously to the server. The buffer size
124/// defaults to 16 and can be configured via
125/// [`RequestOptionsBuilder::with_request_stream_channel_capacity`][crate::options::RequestOptionsBuilder::with_request_stream_channel_capacity].
126///
127/// `RequestSender` is cheaply cloneable. Sending from multiple clones concurrently may
128/// interleave messages; if delivery order is important, send messages sequentially.
129/// The outbound request stream is closed once all clones of the `RequestSender` are dropped.
130///
131/// # Examples
132///
133/// ```
134/// # use google_cloud_gax::streaming::{RequestSender, SendError};
135/// # async fn sample(sender: RequestSender<String>) -> Result<(), SendError> {
136/// sender.send("message 1".to_string()).await?;
137/// sender.send("message 2".to_string()).await?;
138/// drop(sender); // Close outbound request stream
139/// # Ok(())
140/// # }
141/// ```
142#[derive(Clone)]
143pub struct RequestSender<Req> {
144 inner: Arc<SenderFn<Req>>,
145}
146
147impl<Req> std::fmt::Debug for RequestSender<Req> {
148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149 f.debug_struct("RequestSender").finish()
150 }
151}
152
153impl<Req> RequestSender<Req> {
154 /// Sends a request item over the stream.
155 ///
156 /// If the internal request buffer is full, this method awaits asynchronously until
157 /// capacity is available. The buffer size defaults to 16 and can be configured via
158 /// [`RequestOptionsBuilder::with_request_stream_channel_capacity`][crate::options::RequestOptionsBuilder::with_request_stream_channel_capacity].
159 ///
160 /// # Errors
161 ///
162 /// - Returns [`SendError::StreamClosed`] if the stream was closed by the server or receiver.
163 /// - Returns [`SendError::Serialization`] if the request item cannot be serialized.
164 ///
165 /// # Examples
166 ///
167 /// ```
168 /// # use google_cloud_gax::streaming::{RequestSender, SendError};
169 /// # async fn sample(sender: RequestSender<String>) -> Result<(), SendError> {
170 /// sender.send("hello".to_string()).await?;
171 /// # Ok(())
172 /// # }
173 /// ```
174 pub async fn send(&self, item: Req) -> Result<(), SendError> {
175 (self.inner)(item).await
176 }
177
178 /// Creates a [`RequestSender`] from an asynchronous send function.
179 ///
180 /// This constructor is `doc(hidden)` (except when `_internal-semver` is enabled)
181 /// so that generated client transports can construct [`RequestSender`] instances
182 /// that perform pre-send transformations without exposing the closure types or
183 /// wire models in the public API documentation.
184 #[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
185 pub fn from_fn<F, Fut, E>(f: F) -> Self
186 where
187 F: Fn(Req) -> Fut + Send + Sync + 'static,
188 Fut: Future<Output = Result<(), E>> + Send + 'static,
189 E: Into<SendError> + 'static,
190 {
191 Self {
192 inner: Arc::new(move |item| {
193 let fut = f(item);
194 Box::pin(async move { fut.await.map_err(Into::into) })
195 }),
196 }
197 }
198}
199
200/// Creates a [`RequestSender`] from a Tokio [`mpsc::Sender`], useful for mocking in tests.
201///
202/// # Examples
203///
204/// ```
205/// # use google_cloud_gax::streaming::RequestSender;
206/// # use tokio::sync::mpsc;
207/// let (tx, mut rx) = mpsc::channel::<String>(16);
208/// let sender = RequestSender::from(tx);
209/// ```
210impl<Req> From<mpsc::Sender<Req>> for RequestSender<Req>
211where
212 Req: Send + 'static,
213{
214 fn from(req_tx: mpsc::Sender<Req>) -> RequestSender<Req> {
215 Self::from_fn(move |item| {
216 let req_tx = req_tx.clone();
217 async move { req_tx.send(item).await.map_err(|_| SendError::StreamClosed) }
218 })
219 }
220}
221
222/// A boxed pinned future that resolves to an incoming response stream or an error.
223type ConnectingFuture<Resp> =
224 Pin<Box<dyn Future<Output = Result<DynResponseStream<Resp>, crate::error::Error>> + Send>>;
225
226/// A type-erased stream of incoming responses from a gRPC stream.
227///
228/// This wraps an underlying `futures::Stream` in a boxed pinned trait object,
229/// enabling [`ResponseStream`] to perform asynchronous transformations (such
230/// as Protobuf deserialization) without exposing `futures::Stream` in the public API.
231type DynResponseStream<Resp> =
232 Pin<Box<dyn futures::Stream<Item = Result<Resp, crate::error::Error>> + Send>>;
233
234/// Internal state machine for [`ResponseStream`].
235enum ResponseState<Resp> {
236 /// Awaiting the initial connection future to resolve the response stream.
237 Connecting(ConnectingFuture<Resp>),
238 /// Response stream established; actively streaming incoming messages.
239 Connected(DynResponseStream<Resp>),
240 /// Stream has completed or encountered a terminal error.
241 Closed,
242}
243
244/// A handle for receiving inbound response items from a streaming RPC.
245///
246/// Typically, you receive a `ResponseStream` as the result of initiating a streaming RPC.
247/// Unlike [`RequestSender`], `ResponseStream` cannot be cloned and represents exclusive
248/// ownership of the inbound stream. Call [`next`](Self::next) to consume incoming messages
249/// sequentially. Dropping the `ResponseStream` cancels the stream.
250///
251/// Enable the `unstable-stream` feature to convert this type into a [`Stream`][futures::Stream]
252/// via `into_stream`.
253///
254/// # Examples
255///
256/// ```
257/// # use google_cloud_gax::streaming::ResponseStream;
258/// # use google_cloud_gax::Result;
259/// # async fn sample(mut stream: ResponseStream<String>) -> Result<()> {
260/// while let Some(item) = stream.next().await {
261/// let item = item?;
262/// println!("Received: {item}");
263/// }
264/// # Ok(())
265/// # }
266/// ```
267pub struct ResponseStream<Resp> {
268 state: ResponseState<Resp>,
269}
270
271impl<Resp> std::fmt::Debug for ResponseStream<Resp> {
272 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
273 f.debug_struct("ResponseStream").finish()
274 }
275}
276
277impl<Resp> ResponseStream<Resp> {
278 /// Receives the next response message from the stream, or `None` if the stream has finished.
279 ///
280 /// Returns `Some(Err(e))` if a transport, server status, or deserialization error occurs.
281 /// Once an error or stream completion is reached, subsequent calls return `None`.
282 ///
283 /// # Examples
284 ///
285 /// ```
286 /// # use google_cloud_gax::streaming::ResponseStream;
287 /// # use google_cloud_gax::Result;
288 /// # async fn sample(mut stream: ResponseStream<String>) -> Result<()> {
289 /// while let Some(item) = stream.next().await {
290 /// let item = item?;
291 /// println!("Received: {item}");
292 /// }
293 /// # Ok(())
294 /// # }
295 /// ```
296 pub async fn next(&mut self) -> Option<Result<Resp, crate::error::Error>> {
297 use futures::StreamExt as _;
298 loop {
299 match &mut self.state {
300 ResponseState::Connecting(fut) => match fut.await {
301 Ok(stream) => {
302 self.state = ResponseState::Connected(stream);
303 }
304 Err(e) => {
305 self.state = ResponseState::Closed;
306 return Some(Err(e));
307 }
308 },
309 ResponseState::Connected(stream) => {
310 let item = stream.next().await;
311 if item.is_none() {
312 self.state = ResponseState::Closed;
313 }
314 return item;
315 }
316 ResponseState::Closed => return None,
317 }
318 }
319 }
320
321 #[cfg(feature = "unstable-stream")]
322 #[cfg_attr(docsrs, doc(cfg(feature = "unstable-stream")))]
323 /// Converts the `ResponseStream` into an asynchronous [`Stream`][futures::Stream].
324 ///
325 /// # Examples
326 ///
327 /// ```
328 /// # use google_cloud_gax::streaming::ResponseStream;
329 /// # use google_cloud_gax::Result;
330 /// # use futures::StreamExt as _;
331 /// # async fn sample(stream: ResponseStream<String>) -> Result<()> {
332 /// let mut stream = stream.into_stream();
333 /// while let Some(item) = stream.next().await {
334 /// let item = item?;
335 /// println!("Received: {item}");
336 /// }
337 /// # Ok(())
338 /// # }
339 /// ```
340 pub fn into_stream(
341 self,
342 ) -> impl futures::Stream<Item = Result<Resp, crate::error::Error>> + Send + Unpin {
343 Box::pin(futures::stream::unfold(self, |mut s| async move {
344 let item = s.next().await?;
345 Some((item, s))
346 }))
347 }
348
349 /// Creates a [`ResponseStream`] from an asynchronous connection future.
350 ///
351 /// This constructor is `doc(hidden)` (except when `_internal-semver` is enabled)
352 /// so that generated client transports can construct [`ResponseStream`] instances
353 /// that asynchronously await HTTP/2 response headers.
354 #[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
355 pub fn from_future<Fut, S>(fut: Fut) -> Self
356 where
357 Fut: Future<Output = Result<S, crate::error::Error>> + Send + 'static,
358 S: futures::Stream<Item = Result<Resp, crate::error::Error>> + Send + 'static,
359 {
360 let connecting: ConnectingFuture<Resp> = Box::pin(async move {
361 let stream = fut.await?;
362 Ok(Box::pin(stream) as DynResponseStream<Resp>)
363 });
364 Self {
365 state: ResponseState::Connecting(connecting),
366 }
367 }
368
369 /// Creates a [`ResponseStream`] from an asynchronous stream.
370 ///
371 /// This constructor is `doc(hidden)` (except when `_internal-semver` is enabled)
372 /// so that generated client transports can construct [`ResponseStream`] instances
373 /// directly from gRPC response streams without exposing `futures::Stream` in the
374 /// public API documentation.
375 #[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
376 pub fn from_stream<S>(stream: S) -> Self
377 where
378 S: futures::Stream<Item = Result<Resp, crate::error::Error>> + Send + 'static,
379 {
380 Self {
381 state: ResponseState::Connected(Box::pin(stream)),
382 }
383 }
384}
385
386/// Creates a [`ResponseStream`] from a Tokio [`mpsc::Receiver`], useful for mocking in tests.
387///
388/// # Examples
389///
390/// ```
391/// # use google_cloud_gax::streaming::ResponseStream;
392/// # use google_cloud_gax::Result;
393/// # use tokio::sync::mpsc;
394/// let (tx, rx) = mpsc::channel::<Result<String>>(16);
395/// let receiver = ResponseStream::from(rx);
396/// ```
397impl<Resp> From<mpsc::Receiver<crate::Result<Resp>>> for ResponseStream<Resp>
398where
399 Resp: Send + 'static,
400{
401 fn from(rx: mpsc::Receiver<crate::Result<Resp>>) -> Self {
402 Self::from_stream(tokio_stream::wrappers::ReceiverStream::new(rx))
403 }
404}
405
406#[cfg(test)]
407mod tests {
408 use super::*;
409 use std::sync::atomic::{AtomicBool, Ordering};
410
411 #[tokio::test]
412 async fn request_sender_and_response_stream() -> Result<(), Box<dyn std::error::Error>> {
413 let (req_tx, mut req_rx) = mpsc::channel::<String>(16);
414 let (resp_tx, resp_rx) = mpsc::channel::<crate::Result<String>>(16);
415
416 let sender: RequestSender<_> = req_tx.into();
417 let mut stream: ResponseStream<_> = resp_rx.into();
418
419 sender.send("hello".to_string()).await?;
420 assert_eq!(req_rx.recv().await.as_deref(), Some("hello"));
421
422 resp_tx.send(Ok("world".to_string())).await?;
423 assert_eq!(stream.next().await.transpose()?.as_deref(), Some("world"));
424
425 drop(resp_tx);
426 assert!(stream.next().await.is_none());
427 Ok(())
428 }
429
430 #[tokio::test]
431 async fn request_sender_send_error_stream_closed() {
432 let (req_tx, req_rx) = mpsc::channel::<String>(16);
433 let sender = RequestSender::from(req_tx);
434
435 drop(req_rx);
436 let err = sender
437 .send("hello".to_string())
438 .await
439 .expect_err("send should fail when receiver is dropped");
440 assert!(matches!(err, SendError::StreamClosed));
441 assert_eq!(
442 err.to_string(),
443 "cannot send request: stream is closed; inspect ResponseStream for details"
444 );
445 }
446
447 #[tokio::test]
448 async fn request_sender_send_error_serialization() {
449 let sender = RequestSender::from_fn(|item: i32| async move {
450 if item < 0 {
451 Err(SendError::Serialization(Box::new(std::io::Error::other(
452 "negative number",
453 ))))
454 } else {
455 Ok(())
456 }
457 });
458
459 sender.send(42).await.expect("send should succeed");
460 let err = sender
461 .send(-1)
462 .await
463 .expect_err("negative number should trigger serialization error");
464 assert!(matches!(err, SendError::Serialization(_)));
465 assert_eq!(
466 err.to_string(),
467 "cannot serialize the request: negative number"
468 );
469 assert_eq!(format!("{sender:?}"), "RequestSender");
470 }
471
472 #[tokio::test]
473 async fn response_stream_lazy_future_success() -> Result<(), Box<dyn std::error::Error>> {
474 let future_polled = Arc::new(AtomicBool::new(false));
475 let polled_clone = future_polled.clone();
476
477 let lazy_fut = async move {
478 polled_clone.store(true, Ordering::SeqCst);
479 let stream =
480 futures::stream::iter(vec![Ok("item-1".to_string()), Ok("item-2".to_string())]);
481 Ok(stream)
482 };
483
484 let mut stream = ResponseStream::from_future(lazy_fut);
485 // Ensure future is NOT polled before next() is called
486 assert!(!future_polled.load(Ordering::SeqCst));
487
488 // First next() awaits connection future and yields first item
489 let first = stream.next().await.expect("expected first response")?;
490 assert!(future_polled.load(Ordering::SeqCst));
491 assert_eq!(first, "item-1");
492
493 // Second next() yields second item
494 let second = stream.next().await.expect("expected second response")?;
495 assert_eq!(second, "item-2");
496
497 // Third next() returns None (clean EOF)
498 assert!(stream.next().await.is_none());
499
500 // Subsequent next() calls on closed receiver return None
501 assert!(stream.next().await.is_none());
502 assert!(stream.next().await.is_none());
503 assert_eq!(format!("{stream:?}"), "ResponseStream");
504
505 Ok(())
506 }
507
508 #[tokio::test]
509 async fn response_stream_lazy_future_connecting_error() {
510 let status = crate::error::rpc::Status::default()
511 .set_code(crate::error::rpc::Code::PermissionDenied)
512 .set_message("permission denied");
513
514 let lazy_fut = async move {
515 let res: Result<futures::stream::Empty<crate::Result<String>>, _> =
516 Err(crate::error::Error::service(status));
517 res
518 };
519
520 let mut stream = ResponseStream::<String>::from_future(lazy_fut);
521
522 // First next() should return the connection setup error
523 let err = stream
524 .next()
525 .await
526 .expect("expected error item")
527 .expect_err("should be Err");
528 assert_eq!(
529 err.status().map(|s| s.code),
530 Some(crate::error::rpc::Code::PermissionDenied)
531 );
532
533 // After error during connection, stream is closed and returns None
534 assert!(stream.next().await.is_none());
535 assert!(stream.next().await.is_none());
536 }
537
538 #[tokio::test]
539 async fn response_stream_item_error_recovery() -> Result<(), Box<dyn std::error::Error>> {
540 let stream = futures::stream::iter(vec![
541 Ok("item-1".to_string()),
542 Err(crate::error::Error::deser("corrupted item")),
543 Ok("item-2".to_string()),
544 ]);
545 let mut resp = ResponseStream::from_stream(stream);
546
547 let item1 = resp.next().await.expect("expected item 1")?;
548 assert_eq!(item1, "item-1");
549
550 let err = resp
551 .next()
552 .await
553 .expect("expected item 2")
554 .expect_err("item 2 should be deserialization error");
555 assert!(err.is_deserialization());
556
557 let item2 = resp.next().await.expect("expected item 3")?;
558 assert_eq!(item2, "item-2");
559
560 assert!(resp.next().await.is_none());
561 Ok(())
562 }
563
564 #[tokio::test]
565 async fn response_stream_generator_mapping_pipeline() -> Result<(), Box<dyn std::error::Error>>
566 {
567 use futures::StreamExt as _;
568
569 #[derive(Debug, PartialEq)]
570 struct RawProto {
571 text: String,
572 valid: bool,
573 }
574
575 #[derive(Debug, PartialEq)]
576 struct DomainModel {
577 text: String,
578 }
579
580 fn from_proto(raw: RawProto) -> Result<DomainModel, &'static str> {
581 if raw.valid {
582 Ok(DomainModel { text: raw.text })
583 } else {
584 Err("invalid proto payload")
585 }
586 }
587
588 let status = crate::error::rpc::Status::default()
589 .set_code(crate::error::rpc::Code::Unavailable)
590 .set_message("transport unavailable");
591
592 let raw_stream = futures::stream::iter(vec![
593 Ok(RawProto {
594 text: "hello".to_string(),
595 valid: true,
596 }),
597 Err(crate::error::Error::service(status)),
598 Ok(RawProto {
599 text: "corrupted".to_string(),
600 valid: false,
601 }),
602 Ok(RawProto {
603 text: "world".to_string(),
604 valid: true,
605 }),
606 ]);
607
608 let response_stream = raw_stream
609 .map(|res| res.and_then(|raw| from_proto(raw).map_err(crate::error::Error::deser)));
610
611 let mut stream = ResponseStream::from_stream(response_stream);
612
613 // 1. Success
614 let item1 = stream.next().await.expect("expected item 1")?;
615 assert_eq!(
616 item1,
617 DomainModel {
618 text: "hello".to_string()
619 }
620 );
621
622 // 2. Stream transport error
623 let err2 = stream
624 .next()
625 .await
626 .expect("expected item 2")
627 .expect_err("item 2 should be Err");
628 assert_eq!(
629 err2.status().map(|s| s.code),
630 Some(crate::error::rpc::Code::Unavailable)
631 );
632
633 // 3. Deserialization error
634 let err3 = stream
635 .next()
636 .await
637 .expect("expected item 3")
638 .expect_err("item 3 should be Err");
639 assert!(err3.is_deserialization());
640
641 // 4. Success after recoverable error
642 let item4 = stream.next().await.expect("expected item 4")?;
643 assert_eq!(
644 item4,
645 DomainModel {
646 text: "world".to_string()
647 }
648 );
649
650 // 5. Stream finished
651 assert!(stream.next().await.is_none());
652 Ok(())
653 }
654
655 #[cfg(feature = "unstable-stream")]
656 #[tokio::test]
657 async fn response_stream_into_stream() -> Result<(), Box<dyn std::error::Error>> {
658 use futures::StreamExt as _;
659
660 let stream = futures::stream::iter(vec![Ok("first".to_string()), Ok("second".to_string())]);
661 let stream = ResponseStream::from_stream(stream);
662 let mut stream = stream.into_stream();
663
664 assert_eq!(stream.next().await.transpose()?.as_deref(), Some("first"));
665 assert_eq!(stream.next().await.transpose()?.as_deref(), Some("second"));
666 assert!(stream.next().await.is_none());
667 Ok(())
668 }
669}