google_cloud_wkt/
any.rs

1// Copyright 2024 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/// `Any` contains an arbitrary serialized protocol buffer message along with a
16/// URL that describes the type of the serialized message.
17///
18/// Protobuf library provides support to pack/unpack Any values in the form
19/// of utility functions or additional generated methods of the Any type.
20///
21///
22/// # JSON
23///
24/// The JSON representation of an `Any` value uses the regular
25/// representation of the deserialized, embedded message, with an
26/// additional field `@type` which contains the type URL. Example:
27///
28/// ```norust
29///     package google.profile;
30///     message Person {
31///       string first_name = 1;
32///       string last_name = 2;
33///     }
34///
35///     {
36///       "@type": "type.googleapis.com/google.profile.Person",
37///       "firstName": <string>,
38///       "lastName": <string>
39///     }
40/// ```
41///
42/// If the embedded message type is well-known and has a custom JSON
43/// representation, that representation will be embedded adding a field
44/// `value` which holds the custom JSON in addition to the `@type`
45/// field. Example (for message [google.protobuf.Duration][]):
46///
47/// ```norust
48///     {
49///       "@type": "type.googleapis.com/google.protobuf.Duration",
50///       "value": "1.212s"
51///     }
52/// ```
53#[derive(Clone, Debug, Default, PartialEq)]
54#[non_exhaustive]
55pub struct Any(serde_json::Map<String, serde_json::Value>);
56
57/// Indicates a problem trying to use an [Any].
58#[derive(thiserror::Error, Debug)]
59pub enum AnyError {
60    /// Problem serializing an object into an [Any].
61    #[error("cannot serialize object into an Any, source={0:?}")]
62    SerializationError(#[source] BoxedError),
63
64    /// Problem deserializing an object from an [Any].
65    #[error("cannot deserialize from an Any, source={0:?}")]
66    DeserializationError(#[source] BoxedError),
67
68    /// Mismatched type, the [Any] does not contain the desired type.
69    #[error("expected type mismatch in Any deserialization type={0}")]
70    TypeMismatchError(String),
71}
72
73impl AnyError {
74    pub(crate) fn ser<T: Into<BoxedError>>(v: T) -> Self {
75        Self::SerializationError(v.into())
76    }
77
78    pub(crate) fn deser<T: Into<BoxedError>>(v: T) -> Self {
79        Self::DeserializationError(v.into())
80    }
81}
82
83type BoxedError = Box<dyn std::error::Error + Send + Sync>;
84type Error = AnyError;
85
86impl Any {
87    /// Creates a new [Any] from any [Message][crate::message::Message] that
88    /// also supports serialization to JSON.
89    pub fn try_from<T>(message: &T) -> Result<Self, Error>
90    where
91        T: serde::ser::Serialize + crate::message::Message,
92    {
93        let value = message.to_map()?;
94        Ok(Any(value))
95    }
96
97    /// Extracts (if possible) a `T` value from the [Any].
98    pub fn try_into_message<T>(&self) -> Result<T, Error>
99    where
100        T: serde::de::DeserializeOwned + crate::message::Message,
101    {
102        let map = &self.0;
103        let r#type = map
104            .get("@type")
105            .and_then(|v| v.as_str())
106            .ok_or_else(|| "@type field is missing or is not a string".to_string())
107            .map_err(Error::deser)?;
108        Self::check_typename(r#type, T::typename())?;
109        T::from_map(map)
110    }
111
112    fn check_typename(got: &str, want: &str) -> Result<(), Error> {
113        if got == want {
114            return Ok(());
115        }
116        Err(Error::deser(format!("mismatched typenames extracting from Any, the any has {got}, the target type is {want}")))
117    }
118}
119
120impl crate::message::Message for Any {
121    fn typename() -> &'static str {
122        "type.googleapis.com/google.protobuf.Any"
123    }
124    fn to_map(&self) -> Result<crate::message::Map, crate::AnyError> {
125        use serde_json::Value;
126        let map = [
127            ("@type", Value::String(Self::typename().into())),
128            ("value", Value::Object(self.0.clone())),
129        ]
130        .into_iter()
131        .map(|(k, v)| (k.to_string(), v))
132        .collect::<crate::message::Map>();
133        Ok(map)
134    }
135    fn from_map(map: &crate::message::Map) -> Result<Self, crate::AnyError> {
136        crate::message::from_value(map)
137    }
138}
139
140/// Implement [`serde`](::serde) serialization for [Any].
141impl serde::ser::Serialize for Any {
142    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
143    where
144        S: serde::ser::Serializer,
145    {
146        self.0.serialize(serializer)
147    }
148}
149
150/// Implement [`serde`](::serde) deserialization for [Any].
151impl<'de> serde::de::Deserialize<'de> for Any {
152    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
153    where
154        D: serde::Deserializer<'de>,
155    {
156        let value = serde_json::Map::<String, serde_json::Value>::deserialize(deserializer)?;
157        Ok(Any(value))
158    }
159}
160
161#[cfg(test)]
162mod test {
163    use super::*;
164    use crate::duration::*;
165    use crate::empty::Empty;
166    use crate::field_mask::*;
167    use crate::timestamp::*;
168    use serde_json::json;
169    type Result = std::result::Result<(), Box<dyn std::error::Error>>;
170
171    #[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)]
172    #[serde(rename_all = "camelCase")]
173    #[non_exhaustive]
174    pub struct Stored {
175        #[serde(skip_serializing_if = "String::is_empty")]
176        pub parent: String,
177        #[serde(skip_serializing_if = "String::is_empty")]
178        pub id: String,
179    }
180
181    impl crate::message::Message for Stored {
182        fn typename() -> &'static str {
183            "type.googleapis.com/wkt.test.Stored"
184        }
185    }
186
187    #[test]
188    fn serialize_duration() -> Result {
189        let d = Duration::clamp(60, 0);
190        let any = Any::try_from(&d)?;
191        let got = serde_json::to_value(any)?;
192        let want = json!({"@type": "type.googleapis.com/google.protobuf.Duration", "value": "60s"});
193        assert_eq!(got, want);
194        Ok(())
195    }
196
197    #[test]
198    fn deserialize_duration() -> Result {
199        let input =
200            json!({"@type": "type.googleapis.com/google.protobuf.Duration", "value": "60s"});
201        let any = Any(input.as_object().unwrap().clone());
202        let d = any.try_into_message::<Duration>()?;
203        assert_eq!(d, Duration::clamp(60, 0));
204        Ok(())
205    }
206
207    #[test]
208    fn serialize_empty() -> Result {
209        let empty = Empty::default();
210        let any = Any::try_from(&empty)?;
211        let got = serde_json::to_value(any)?;
212        let want = json!({"@type": "type.googleapis.com/google.protobuf.Empty"});
213        assert_eq!(got, want);
214        Ok(())
215    }
216
217    #[test]
218    fn deserialize_empty() -> Result {
219        let input = json!({"@type": "type.googleapis.com/google.protobuf.Empty"});
220        let any = Any(input.as_object().unwrap().clone());
221        let empty = any.try_into_message::<Empty>()?;
222        assert_eq!(empty, Empty::default());
223        Ok(())
224    }
225
226    #[test]
227    fn serialize_field_mask() -> Result {
228        let d = FieldMask::default().set_paths(["a", "b"].map(str::to_string).to_vec());
229        let any = Any::try_from(&d)?;
230        let got = serde_json::to_value(any)?;
231        let want =
232            json!({"@type": "type.googleapis.com/google.protobuf.FieldMask", "paths": "a,b"});
233        assert_eq!(got, want);
234        Ok(())
235    }
236
237    #[test]
238    fn deserialize_field_mask() -> Result {
239        let input =
240            json!({"@type": "type.googleapis.com/google.protobuf.FieldMask", "paths": "a,b"});
241        let any = Any(input.as_object().unwrap().clone());
242        let d = any.try_into_message::<FieldMask>()?;
243        assert_eq!(
244            d,
245            FieldMask::default().set_paths(["a", "b"].map(str::to_string).to_vec())
246        );
247        Ok(())
248    }
249
250    #[test]
251    fn serialize_timestamp() -> Result {
252        let d = Timestamp::clamp(123, 0);
253        let any = Any::try_from(&d)?;
254        let got = serde_json::to_value(any)?;
255        let want = json!({"@type": "type.googleapis.com/google.protobuf.Timestamp", "value": "1970-01-01T00:02:03Z"});
256        assert_eq!(got, want);
257        Ok(())
258    }
259
260    #[test]
261    fn deserialize_timestamp() -> Result {
262        let input = json!({"@type": "type.googleapis.com/google.protobuf.Timestamp", "value": "1970-01-01T00:02:03Z"});
263        let any = Any(input.as_object().unwrap().clone());
264        let d = any.try_into_message::<Timestamp>()?;
265        assert_eq!(d, Timestamp::clamp(123, 0));
266        Ok(())
267    }
268
269    #[test]
270    fn serialize_generic() -> Result {
271        let d = Stored {
272            parent: "parent".to_string(),
273            id: "id".to_string(),
274        };
275        let any = Any::try_from(&d)?;
276        let got = serde_json::to_value(any)?;
277        let want =
278            json!({"@type": "type.googleapis.com/wkt.test.Stored", "parent": "parent", "id": "id"});
279        assert_eq!(got, want);
280        Ok(())
281    }
282
283    #[test]
284    fn deserialize_generic() -> Result {
285        let input =
286            json!({"@type": "type.googleapis.com/wkt.test.Stored", "parent": "parent", "id": "id"});
287        let any = Any(input.as_object().unwrap().clone());
288        let d = any.try_into_message::<Stored>()?;
289        assert_eq!(
290            d,
291            Stored {
292                parent: "parent".to_string(),
293                id: "id".to_string()
294            }
295        );
296        Ok(())
297    }
298
299    #[derive(Default, serde::Serialize, serde::Deserialize)]
300    struct DetectBadMessages(serde_json::Value);
301    impl crate::message::Message for DetectBadMessages {
302        fn typename() -> &'static str {
303            "not used"
304        }
305    }
306
307    #[test]
308    fn try_from_error() -> Result {
309        let input = DetectBadMessages(json!([2, 3]));
310        let got = Any::try_from(&input);
311        assert!(got.is_err(), "{got:?}");
312
313        Ok(())
314    }
315
316    #[test]
317    fn deserialize_missing_type_field() -> Result {
318        let input = json!({"@type-is-missing": ""});
319        let any = serde_json::from_value::<Any>(input)?;
320        let got = any.try_into_message::<Stored>();
321        assert!(got.is_err());
322        Ok(())
323    }
324
325    #[test]
326    fn deserialize_invalid_type_field() -> Result {
327        let input = json!({"@type": [1, 2, 3]});
328        let any = serde_json::from_value::<Any>(input)?;
329        let got = any.try_into_message::<Stored>();
330        assert!(got.is_err());
331        Ok(())
332    }
333
334    #[test]
335    fn deserialize_missing_value_field() -> Result {
336        let input = json!({"@type": "type.googleapis.com/google.protobuf.Duration", "value-is-missing": "1.2s"});
337        let any = serde_json::from_value::<Any>(input)?;
338        let got = any.try_into_message::<Duration>();
339        assert!(got.is_err());
340        Ok(())
341    }
342
343    #[test]
344    fn deserialize_invalid_value_field() -> Result {
345        let input =
346            json!({"@type": "type.googleapis.com/google.protobuf.Duration", "value": ["1.2s"]});
347        let any = serde_json::from_value::<Any>(input)?;
348        let got = any.try_into_message::<Duration>();
349        assert!(got.is_err());
350        Ok(())
351    }
352
353    #[test]
354    fn deserialize_type_mismatch() -> Result {
355        let input =
356            json!({"@type": "type.googleapis.com/google.protobuf.Duration", "value": "1.2s"});
357        let any = serde_json::from_value::<Any>(input)?;
358        let got = any.try_into_message::<Timestamp>();
359        assert!(got.is_err());
360        let error = got.err().unwrap();
361        assert!(
362            format!("{error}").contains("type.googleapis.com/google.protobuf.Duration"),
363            "{error}"
364        );
365        assert!(
366            format!("{error}").contains("type.googleapis.com/google.protobuf.Timestamp"),
367            "{error}"
368        );
369        Ok(())
370    }
371}