origincloud_core/
operation.rs1use std::fmt;
2use std::marker::PhantomData;
3use std::time::Duration;
4
5use serde::de::DeserializeOwned;
6
7use crate::error::ApiError;
8use crate::longrunning::OperationResult;
9use crate::transport::is_timeout;
10use crate::{Error, Method, Request, Service, longrunning, path_value};
11
12pub struct Operation<T> {
16 service: Service,
17 operation: longrunning::Operation,
18 result_type: &'static str,
19 result: PhantomData<fn() -> T>,
20}
21
22impl<T: DeserializeOwned> Operation<T> {
23 pub fn new(
29 service: Service,
30 operation: longrunning::Operation,
31 result_type: &'static str,
32 ) -> Self {
33 Self {
34 service: service.with_options(crate::CallOptions::default()),
35 operation,
36 result_type,
37 result: PhantomData,
38 }
39 }
40
41 pub fn name(&self) -> &str {
44 &self.operation.name
45 }
46
47 pub fn done(&self) -> bool {
49 self.operation.done
50 }
51
52 pub fn raw(&self) -> &longrunning::Operation {
54 &self.operation
55 }
56
57 pub async fn wait(&mut self) -> Result<T, Error> {
76 let body = serde_json::json!({ "timeout": format!("{}s", self.wait_per_call().as_secs()) });
77 while !self.operation.done {
78 let path = path_value(
79 "operation name",
80 &self.operation.name,
81 "projects/*/zones/*/operations/*",
82 )?;
83 let mut call = Request::new(Method::POST, format!("/v1beta1/{path}:wait"))
84 .with_body(body.to_string().into_bytes())
85 .idempotent();
86 call.timeout_answers = true;
87 match self.service.call(call).await {
88 Ok(operation) => self.operation = operation,
89 Err(err) if is_timeout(&err) => {}
90 Err(err) => return Err(err),
91 }
92 }
93 self.result()
94 }
95
96 fn wait_per_call(&self) -> Duration {
100 let inner = &self.service.client.inner;
101 let mut wait = inner.operation_wait;
102 if let Some(timeout) = inner.timeout {
103 let margin = Duration::from_secs(1).min(timeout / 2);
104 wait = wait.min(timeout - margin);
105 }
106 Duration::from_secs(wait.as_secs().max(1))
107 }
108
109 pub fn result(&self) -> Result<T, Error> {
113 let name = &self.operation.name;
114 if !self.operation.done {
115 return Err(Error::NotDone(name.clone()));
116 }
117 match &self.operation.result {
118 Some(OperationResult::Error(status)) => Err(ApiError::from_status(status).into()),
119 Some(OperationResult::Response(response)) => {
120 if response.type_name() != self.result_type {
121 return Err(Error::Decode(format!(
122 "the result of operation {name} is a {}, not a {}",
123 response.type_name(),
124 self.result_type
125 )));
126 }
127 response.decode().map_err(|e| {
128 Error::Decode(format!("reading the result of operation {name}: {e}"))
129 })
130 }
131 None => Err(Error::Decode(format!(
132 "operation {name} is done without a result"
133 ))),
134 }
135 }
136}
137
138impl<T> fmt::Debug for Operation<T> {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 f.debug_struct("Operation")
141 .field("name", &self.operation.name)
142 .field("done", &self.operation.done)
143 .field("result_type", &self.result_type)
144 .finish_non_exhaustive()
145 }
146}