Skip to main content

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}