use std::fmt;
use std::marker::PhantomData;
use std::time::Duration;
use serde::de::DeserializeOwned;
use crate::error::ApiError;
use crate::longrunning::OperationResult;
use crate::transport::is_timeout;
use crate::{Error, Method, Request, Service, longrunning, path_value};
pub struct Operation<T> {
service: Service,
operation: longrunning::Operation,
result_type: &'static str,
result: PhantomData<fn() -> T>,
}
impl<T: DeserializeOwned> Operation<T> {
pub fn new(
service: Service,
operation: longrunning::Operation,
result_type: &'static str,
) -> Self {
Self {
service,
operation,
result_type,
result: PhantomData,
}
}
pub fn name(&self) -> &str {
&self.operation.name
}
pub fn done(&self) -> bool {
self.operation.done
}
pub fn raw(&self) -> &longrunning::Operation {
&self.operation
}
pub async fn wait(&mut self) -> Result<T, Error> {
let body = serde_json::json!({ "timeout": format!("{}s", self.wait_per_call().as_secs()) });
while !self.operation.done {
let path = path_value(
"operation name",
&self.operation.name,
"projects/*/zones/*/operations/*",
)?;
let mut call = Request::new(Method::POST, format!("/v1beta1/{path}:wait"))
.with_body(body.to_string().into_bytes())
.idempotent();
call.timeout_answers = true;
match self.service.call(call).await {
Ok(operation) => self.operation = operation,
Err(err) if is_timeout(&err) => {}
Err(err) => return Err(err),
}
}
self.result()
}
fn wait_per_call(&self) -> Duration {
let inner = &self.service.client.inner;
let mut wait = inner.operation_wait;
if let Some(timeout) = inner.timeout {
let margin = Duration::from_secs(1).min(timeout / 2);
wait = wait.min(timeout - margin);
}
Duration::from_secs(wait.as_secs().max(1))
}
pub fn result(&self) -> Result<T, Error> {
let name = &self.operation.name;
if !self.operation.done {
return Err(Error::NotDone(name.clone()));
}
match &self.operation.result {
Some(OperationResult::Error(status)) => Err(ApiError::from_status(status).into()),
Some(OperationResult::Response(response)) => {
if response.type_name() != self.result_type {
return Err(Error::Decode(format!(
"the result of operation {name} is a {}, not a {}",
response.type_name(),
self.result_type
)));
}
response.decode().map_err(|e| {
Error::Decode(format!("reading the result of operation {name}: {e}"))
})
}
None => Err(Error::Decode(format!(
"operation {name} is done without a result"
))),
}
}
}
impl<T> fmt::Debug for Operation<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Operation")
.field("name", &self.operation.name)
.field("done", &self.operation.done)
.field("result_type", &self.result_type)
.finish_non_exhaustive()
}
}