google_cloud_lro/lib.rs
1// Copyright 2025 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//! Types and functions to make LROs easier to use and to require less boilerplate.
16//!
17//! Occasionally, a Google Cloud service may need to expose a method that takes
18//! a significant amount of time to complete. In these situations, it is often
19//! a poor user experience to simply block while the task runs. Such services
20//! return a long-running operation, a type of promise that can be polled until
21//! it completes successfully.
22//!
23//! Polling these operations can be tedious. The application needs to
24//! periodically make RPCs, extract the result from the response, and may need
25//! to implement a stream to return metadata representing any progress in the
26//! RPC.
27//!
28//! The Google Cloud client libraries for Rust return implementations of this
29//! trait to simplify working with these long-running operations.
30//!
31//! # Example: automatically poll until completion
32//! ```no_run
33//! # use google_cloud_lro::{internal::Operation, Poller};
34//! # use serde::{Deserialize, Serialize};
35//! # use gax::Result;
36//! # use wkt::Timestamp as Response;
37//! # use wkt::Duration as Metadata;
38//! async fn start_lro() -> impl Poller<Response, Metadata> {
39//! // ... details omitted ...
40//! # async fn start() -> Result<Operation<Response, Metadata>> { panic!(); }
41//! # async fn query(_: String) -> Result<Operation<Response, Metadata>> { panic!(); }
42//! # google_cloud_lro::internal::new_poller(
43//! # std::sync::Arc::new(gax::polling_error_policy::AlwaysContinue),
44//! # std::sync::Arc::new(gax::exponential_backoff::ExponentialBackoff::default()),
45//! # start, query
46//! # )
47//! }
48//! # tokio_test::block_on(async {
49//! let response = start_lro()
50//! .await
51//! .until_done()
52//! .await?;
53//! println!("response = {response:?}");
54//! # gax::Result::<()>::Ok(()) });
55//! ```
56//!
57//! # Example: poll with metadata
58//! ```no_run
59//! # use google_cloud_lro::{internal::Operation, Poller, PollingResult};
60//! # use serde::{Deserialize, Serialize};
61//! # use gax::Result;
62//! # use wkt::Timestamp as Response;
63//! # use wkt::Duration as Metadata;
64//!
65//! async fn start_lro() -> impl Poller<Response, Metadata> {
66//! // ... details omitted ...
67//! # async fn start() -> Result<Operation<Response, Metadata>> { panic!(); }
68//! # async fn query(_: String) -> Result<Operation<Response, Metadata>> { panic!(); }
69//! # google_cloud_lro::internal::new_poller(
70//! # std::sync::Arc::new(gax::polling_error_policy::AlwaysContinue),
71//! # std::sync::Arc::new(gax::exponential_backoff::ExponentialBackoff::default()),
72//! # start, query
73//! # )
74//! }
75//! # tokio_test::block_on(async {
76//! let mut poller = start_lro().await;
77//! while let Some(p) = poller.poll().await {
78//! match p {
79//! PollingResult::Completed(r) => { println!("LRO completed, response={r:?}"); }
80//! PollingResult::InProgress(m) => { println!("LRO in progress, metadata={m:?}"); }
81//! PollingResult::PollingError(e) => { println!("Transient error polling the LRO: {e}"); }
82//! }
83//! tokio::time::sleep(std::time::Duration::from_millis(100)).await;
84//! }
85//! # gax::Result::<()>::Ok(()) });
86//! ```
87
88#![cfg_attr(docsrs, feature(doc_cfg))]
89
90use gax::Result;
91use gax::error::Error;
92use gax::polling_backoff_policy::PollingBackoffPolicy;
93use gax::polling_error_policy::PollingErrorPolicy;
94use std::future::Future;
95
96/// The result of polling a Long-Running Operation (LRO).
97///
98/// # Parameters
99/// * `ResponseType` - This is the type returned when the LRO completes
100/// successfully.
101/// * `MetadataType` - The LRO may return values of this type while the
102/// operation is in progress. This may include some measure of "progress".
103#[derive(Debug)]
104pub enum PollingResult<ResponseType, MetadataType> {
105 /// The operation is still in progress.
106 InProgress(Option<MetadataType>),
107 /// The operation completed. This includes the result.
108 Completed(Result<ResponseType>),
109 /// An error trying to poll the LRO.
110 ///
111 /// Not all errors indicate that the operation failed. For example, this
112 /// may fail because it was not possible to connect to Google Cloud. Such
113 /// transient errors may disappear in the next polling attempt.
114 ///
115 /// Other errors will never recover. For example, a [ServiceError] with
116 /// a [NOT_FOUND], [ABORTED], or [PERMISSION_DENIED] code will never
117 /// recover.
118 ///
119 /// [ServiceError]: gax::error::ServiceError
120 /// [NOT_FOUND]: rpc::model::Code::NotFound
121 /// [ABORTED]: rpc::model::Code::Aborted
122 /// [PERMISSION_DENIED]: rpc::model::Code::PermissionDenied
123 PollingError(Error),
124}
125
126#[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
127pub mod internal;
128
129mod sealed {
130 pub trait Poller {}
131}
132
133/// Automatically polls long-running operations.
134///
135/// # Parameters
136/// * `ResponseType` - This is the type returned when the LRO completes
137/// successfully.
138/// * `MetadataType` - The LRO may return values of this type while the
139/// operation is in progress. This may include some measure of "progress".
140pub trait Poller<ResponseType, MetadataType>: Send + sealed::Poller {
141 /// Query the current status of the long-running operation.
142 fn poll(
143 &mut self,
144 ) -> impl Future<Output = Option<PollingResult<ResponseType, MetadataType>>> + Send;
145
146 /// Poll the long-running operation until it completes.
147 fn until_done(self) -> impl Future<Output = Result<ResponseType>> + Send;
148
149 /// Convert a poller to a [Stream][futures::Stream].
150 #[cfg(feature = "unstable-stream")]
151 #[cfg_attr(docsrs, doc(cfg(feature = "unstable-stream")))]
152 fn into_stream(self) -> impl futures::Stream<Item = PollingResult<ResponseType, MetadataType>>;
153}
154
155mod details;