Skip to main content

origincloud_core/longrunning/
mod.rs

1//! Long-running operations: what a service's creates, updates and deletes
2//! answer with, until their resource settles.
3//!
4//! A method that starts one returns an [`Operation`](crate::Operation) to
5//! wait on. This module holds the operation as the API sends it, and the
6//! `OperationService` every service serves at its own host, for resuming
7//! an operation by its name:
8//!
9//! ```no_run
10//! # async fn resume(volumes: &origincloud_core::Service) -> Result<(), origincloud_core::Error> {
11//! use origincloud_core::longrunning::v1beta1::{GetOperationRequest, OperationClient};
12//!
13//! let operations = OperationClient::new(volumes);
14//! let operation = operations
15//!     .get_operation(GetOperationRequest {
16//!         name: "projects/my-project-123/zones/eu-central1-az1/operations/op-1".to_owned(),
17//!     })
18//!     .await?;
19//! println!("{} done: {}", operation.name, operation.done);
20//! # Ok(())
21//! # }
22//! ```
23
24mod generated;
25
26pub use generated::*;
27
28use serde::{Deserialize, Serialize};
29
30use crate::rpc::{Any, Status};
31
32/// A long-running operation (`google.longrunning.Operation`), as the API
33/// sends it.
34#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
35#[serde(from = "Wire", into = "Wire")]
36pub struct Operation {
37    /// The operation's resource name,
38    /// `projects/{project}/zones/{zone}/operations/{operation}`.
39    pub name: String,
40    /// What the operation is about: an
41    /// [`OperationMetadata`](v1beta1::OperationMetadata).
42    pub metadata: Option<Any>,
43    /// Whether it is done.
44    pub done: bool,
45    /// What it ended with, once it is done.
46    pub result: Option<OperationResult>,
47}
48
49/// What an operation ended with.
50#[derive(Clone, Debug, PartialEq)]
51pub enum OperationResult {
52    /// It failed with this error.
53    Error(Status),
54    /// It succeeded with this result: the resource it acted on, or
55    /// `google.protobuf.Empty` for a delete.
56    Response(Any),
57}
58
59/// An operation's JSON form.
60#[derive(Default, Serialize, Deserialize)]
61#[serde(default, rename_all = "camelCase")]
62struct Wire {
63    #[serde(skip_serializing_if = "String::is_empty")]
64    name: String,
65    #[serde(skip_serializing_if = "Option::is_none")]
66    metadata: Option<Any>,
67    #[serde(skip_serializing_if = "std::ops::Not::not")]
68    done: bool,
69    #[serde(skip_serializing_if = "Option::is_none")]
70    error: Option<Status>,
71    #[serde(skip_serializing_if = "Option::is_none")]
72    response: Option<Any>,
73}
74
75impl From<Wire> for Operation {
76    fn from(w: Wire) -> Self {
77        let result = match (w.error, w.response) {
78            (Some(error), _) => Some(OperationResult::Error(error)),
79            (None, Some(response)) => Some(OperationResult::Response(response)),
80            (None, None) => None,
81        };
82        Self {
83            name: w.name,
84            metadata: w.metadata,
85            done: w.done,
86            result,
87        }
88    }
89}
90
91impl From<Operation> for Wire {
92    fn from(o: Operation) -> Self {
93        let (error, response) = match o.result {
94            Some(OperationResult::Error(e)) => (Some(e), None),
95            Some(OperationResult::Response(r)) => (None, Some(r)),
96            None => (None, None),
97        };
98        Self {
99            name: o.name,
100            metadata: o.metadata,
101            done: o.done,
102            error,
103            response,
104        }
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn an_operation_reads_its_result() {
114        let done: Operation = serde_json::from_str(
115            r#"{"name":"projects/p/zones/z/operations/op-1","done":true,"response":{"@type":"type.googleapis.com/x.Y","name":"n"},"unknown":1}"#,
116        )
117        .unwrap();
118        assert!(done.done);
119        let Some(OperationResult::Response(any)) = &done.result else {
120            panic!("{done:?}");
121        };
122        assert_eq!(any.type_name(), "x.Y");
123        let again: Operation =
124            serde_json::from_str(&serde_json::to_string(&done).unwrap()).unwrap();
125        assert_eq!(again, done);
126
127        let running: Operation = serde_json::from_str(r#"{"name":"n"}"#).unwrap();
128        assert!(!running.done && running.result.is_none());
129    }
130}