Skip to main content

origincloud_core/
rpc.rs

1//! `google.rpc.Status` and `google.protobuf.Any` in their JSON form: what
2//! a failed operation carries, and the details of an error.
3
4use serde::de::{DeserializeOwned, Error as _};
5use serde::ser::SerializeMap;
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7use serde_json::{Map, Value};
8
9/// The error a call or an operation failed with (`google.rpc.Status`).
10#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
11#[serde(default, rename_all = "camelCase")]
12pub struct Status {
13    /// The canonical code, such as 5 for `NOT_FOUND`.
14    pub code: i32,
15    /// What went wrong, for a person.
16    pub message: String,
17    /// Details for a program, such as the `google.rpc.ErrorInfo` naming the
18    /// reason.
19    #[serde(skip_serializing_if = "Vec::is_empty")]
20    pub details: Vec<Any>,
21}
22
23/// A message of any type, with the type it is (`google.protobuf.Any`), in
24/// its JSON form: the message's fields next to `@type`.
25#[derive(Clone, Debug, Default, PartialEq)]
26pub struct Any {
27    type_url: String,
28    fields: Map<String, Value>,
29}
30
31impl Any {
32    /// An `Any` of type `type_url`, such as
33    /// `type.googleapis.com/google.rpc.ErrorInfo`, holding fields.
34    pub fn new(type_url: impl Into<String>, fields: Map<String, Value>) -> Self {
35        Self {
36            type_url: type_url.into(),
37            fields,
38        }
39    }
40
41    /// The type URL, such as `type.googleapis.com/google.rpc.ErrorInfo`.
42    pub fn type_url(&self) -> &str {
43        &self.type_url
44    }
45
46    /// The type's full name, such as `google.rpc.ErrorInfo`: the type URL
47    /// after its last `/`.
48    pub fn type_name(&self) -> &str {
49        self.type_url.rsplit('/').next().unwrap_or_default()
50    }
51
52    /// The message's fields, by their JSON names.
53    pub fn fields(&self) -> &Map<String, Value> {
54        &self.fields
55    }
56
57    /// The message, read as `T`. A well-known type with a JSON form of its
58    /// own, such as `google.protobuf.Empty`, is read from `value`.
59    pub fn decode<T: DeserializeOwned>(&self) -> Result<T, serde_json::Error> {
60        if self.type_name().starts_with("google.protobuf.")
61            && let Some(value) = self.fields.get("value")
62        {
63            return T::deserialize(value);
64        }
65        T::deserialize(Value::Object(self.fields.clone()))
66    }
67
68    /// A string field, or `""` when the message has none by that name.
69    pub(crate) fn string(&self, name: &str) -> &str {
70        self.fields
71            .get(name)
72            .and_then(Value::as_str)
73            .unwrap_or_default()
74    }
75}
76
77impl Serialize for Any {
78    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
79        let mut map = serializer.serialize_map(Some(self.fields.len() + 1))?;
80        map.serialize_entry("@type", &self.type_url)?;
81        for (name, value) in &self.fields {
82            map.serialize_entry(name, value)?;
83        }
84        map.end()
85    }
86}
87
88impl<'de> Deserialize<'de> for Any {
89    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
90        let mut fields = Map::deserialize(deserializer)?;
91        let type_url = match fields.remove("@type") {
92            Some(Value::String(url)) => url,
93            Some(_) => return Err(D::Error::custom("@type isn't a string")),
94            None => String::new(),
95        };
96        Ok(Self { type_url, fields })
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn an_any_keeps_its_type_apart_from_its_fields() {
106        let any: Any = serde_json::from_str(
107            r#"{"@type":"type.googleapis.com/google.rpc.ErrorInfo","reason":"NOT_FOUND"}"#,
108        )
109        .unwrap();
110        assert_eq!(any.type_name(), "google.rpc.ErrorInfo");
111        assert_eq!(any.string("reason"), "NOT_FOUND");
112        assert_eq!(
113            serde_json::to_string(&any).unwrap(),
114            r#"{"@type":"type.googleapis.com/google.rpc.ErrorInfo","reason":"NOT_FOUND"}"#
115        );
116    }
117
118    #[test]
119    fn a_well_known_type_is_read_from_its_value() {
120        let any: Any = serde_json::from_str(
121            r#"{"@type":"type.googleapis.com/google.protobuf.Empty","value":{}}"#,
122        )
123        .unwrap();
124        any.decode::<crate::protobuf::Empty>().unwrap();
125        let bare: Any =
126            serde_json::from_str(r#"{"@type":"type.googleapis.com/google.protobuf.Empty"}"#)
127                .unwrap();
128        bare.decode::<crate::protobuf::Empty>().unwrap();
129    }
130}