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    /// Returns the name of the contained type.
88    ///
89    /// An any may contain any message type. The name of the message is a URL,
90    /// usually with the `https://` scheme elided. All types in Google Cloud
91    /// APIs are of the form
92    /// `https://type.googleapis.com/${fully-qualified-name}`.
93    ///
94    /// Note that this is not an available URL where you can download data (such
95    /// as the message schema) from.
96    pub fn type_url(&self) -> Option<&str> {
97        self.0.get("@type").and_then(serde_json::Value::as_str)
98    }
99
100    /// Creates a new [Any] from any [Message][crate::message::Message] that
101    /// also supports serialization to JSON.
102    pub fn try_from<T>(message: &T) -> Result<Self, Error>
103    where
104        T: serde::ser::Serialize + crate::message::Message,
105    {
106        let value = message.to_map()?;
107        Ok(Any(value))
108    }
109
110    /// Extracts (if possible) a `T` value from the [Any].
111    pub fn try_into_message<T>(&self) -> Result<T, Error>
112    where
113        T: serde::de::DeserializeOwned + crate::message::Message,
114    {
115        let map = &self.0;
116        let r#type = map
117            .get("@type")
118            .and_then(|v| v.as_str())
119            .ok_or_else(|| "@type field is missing or is not a string".to_string())
120            .map_err(Error::deser)?;
121        Self::check_typename(r#type, T::typename())?;
122        T::from_map(map)
123    }
124
125    fn check_typename(got: &str, want: &str) -> Result<(), Error> {
126        if got == want {
127            return Ok(());
128        }
129        Err(Error::deser(format!(
130            "mismatched typenames extracting from Any, the any has {got}, the target type is {want}"
131        )))
132    }
133}
134
135impl crate::message::Message for Any {
136    fn typename() -> &'static str {
137        "type.googleapis.com/google.protobuf.Any"
138    }
139    fn to_map(&self) -> Result<crate::message::Map, AnyError> {
140        use serde_json::Value;
141        let map = [
142            ("@type", Value::String(Self::typename().into())),
143            ("value", Value::Object(self.0.clone())),
144        ]
145        .into_iter()
146        .map(|(k, v)| (k.to_string(), v))
147        .collect::<crate::message::Map>();
148        Ok(map)
149    }
150    fn from_map(map: &crate::message::Map) -> Result<Self, AnyError> {
151        crate::message::from_value(map)
152    }
153}
154
155/// Implement [`serde`](::serde) serialization for [Any].
156impl serde::ser::Serialize for Any {
157    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
158    where
159        S: serde::ser::Serializer,
160    {
161        self.0.serialize(serializer)
162    }
163}
164
165/// Implement [`serde`](::serde) deserialization for [Any].
166impl<'de> serde::de::Deserialize<'de> for Any {
167    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
168    where
169        D: serde::Deserializer<'de>,
170    {
171        let value = serde_json::Map::<String, serde_json::Value>::deserialize(deserializer)?;
172        Ok(Any(value))
173    }
174}
175
176#[cfg(test)]
177mod test {
178    use super::*;
179    use crate::duration::*;
180    use crate::empty::Empty;
181    use crate::field_mask::*;
182    use crate::timestamp::*;
183    use serde_json::json;
184    type Result = std::result::Result<(), Box<dyn std::error::Error>>;
185
186    #[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)]
187    #[serde(rename_all = "camelCase")]
188    #[non_exhaustive]
189    pub struct Stored {
190        #[serde(skip_serializing_if = "String::is_empty")]
191        pub parent: String,
192        #[serde(skip_serializing_if = "String::is_empty")]
193        pub id: String,
194    }
195
196    impl crate::message::Message for Stored {
197        fn typename() -> &'static str {
198            "type.googleapis.com/wkt.test.Stored"
199        }
200    }
201
202    #[test]
203    fn serialize_any() -> Result {
204        let d = Duration::clamp(60, 0);
205        let any = Any::try_from(&d)?;
206        let any = Any::try_from(&any)?;
207        assert_eq!(
208            any.type_url(),
209            Some("type.googleapis.com/google.protobuf.Any")
210        );
211        let got = serde_json::to_value(any)?;
212        let want = json!({
213            "@type": "type.googleapis.com/google.protobuf.Any",
214            "value": {
215                "@type": "type.googleapis.com/google.protobuf.Duration",
216                "value": "60s"
217            }
218        });
219        assert_eq!(got, want);
220        Ok(())
221    }
222
223    #[test]
224    fn deserialize_any() -> Result {
225        let input = json!({
226            "@type": "type.googleapis.com/google.protobuf.Any",
227            "value": {
228                "@type": "type.googleapis.com/google.protobuf.Duration",
229                "value": "60s"
230            }
231        });
232        let any = Any(input.as_object().unwrap().clone());
233        assert_eq!(
234            any.type_url(),
235            Some("type.googleapis.com/google.protobuf.Any")
236        );
237        let any = any.try_into_message::<Any>()?;
238        assert_eq!(
239            any.type_url(),
240            Some("type.googleapis.com/google.protobuf.Duration")
241        );
242        let d = any.try_into_message::<Duration>()?;
243        assert_eq!(d, Duration::clamp(60, 0));
244        Ok(())
245    }
246
247    #[test]
248    fn serialize_duration() -> Result {
249        let d = Duration::clamp(60, 0);
250        let any = Any::try_from(&d)?;
251        assert_eq!(
252            any.type_url(),
253            Some("type.googleapis.com/google.protobuf.Duration")
254        );
255        let got = serde_json::to_value(any)?;
256        let want = json!({"@type": "type.googleapis.com/google.protobuf.Duration", "value": "60s"});
257        assert_eq!(got, want);
258        Ok(())
259    }
260
261    #[test]
262    fn deserialize_duration() -> Result {
263        let input =
264            json!({"@type": "type.googleapis.com/google.protobuf.Duration", "value": "60s"});
265        let any = Any(input.as_object().unwrap().clone());
266        assert_eq!(
267            any.type_url(),
268            Some("type.googleapis.com/google.protobuf.Duration")
269        );
270        let d = any.try_into_message::<Duration>()?;
271        assert_eq!(d, Duration::clamp(60, 0));
272        Ok(())
273    }
274
275    #[test]
276    fn serialize_empty() -> Result {
277        let empty = Empty::default();
278        let any = Any::try_from(&empty)?;
279        assert_eq!(
280            any.type_url(),
281            Some("type.googleapis.com/google.protobuf.Empty")
282        );
283        let got = serde_json::to_value(any)?;
284        let want = json!({"@type": "type.googleapis.com/google.protobuf.Empty"});
285        assert_eq!(got, want);
286        Ok(())
287    }
288
289    #[test]
290    fn deserialize_empty() -> Result {
291        let input = json!({"@type": "type.googleapis.com/google.protobuf.Empty"});
292        let any = Any(input.as_object().unwrap().clone());
293        assert_eq!(
294            any.type_url(),
295            Some("type.googleapis.com/google.protobuf.Empty")
296        );
297        let empty = any.try_into_message::<Empty>()?;
298        assert_eq!(empty, Empty::default());
299        Ok(())
300    }
301
302    #[test]
303    fn serialize_field_mask() -> Result {
304        let d = FieldMask::default().set_paths(["a", "b"].map(str::to_string).to_vec());
305        let any = Any::try_from(&d)?;
306        assert_eq!(
307            any.type_url(),
308            Some("type.googleapis.com/google.protobuf.FieldMask")
309        );
310        let got = serde_json::to_value(any)?;
311        let want =
312            json!({"@type": "type.googleapis.com/google.protobuf.FieldMask", "paths": "a,b"});
313        assert_eq!(got, want);
314        Ok(())
315    }
316
317    #[test]
318    fn deserialize_field_mask() -> Result {
319        let input =
320            json!({"@type": "type.googleapis.com/google.protobuf.FieldMask", "paths": "a,b"});
321        let any = Any(input.as_object().unwrap().clone());
322        assert_eq!(
323            any.type_url(),
324            Some("type.googleapis.com/google.protobuf.FieldMask")
325        );
326        let d = any.try_into_message::<FieldMask>()?;
327        assert_eq!(
328            d,
329            FieldMask::default().set_paths(["a", "b"].map(str::to_string).to_vec())
330        );
331        Ok(())
332    }
333
334    #[test]
335    fn serialize_timestamp() -> Result {
336        let d = Timestamp::clamp(123, 0);
337        let any = Any::try_from(&d)?;
338        assert_eq!(
339            any.type_url(),
340            Some("type.googleapis.com/google.protobuf.Timestamp")
341        );
342        let got = serde_json::to_value(any)?;
343        let want = json!({"@type": "type.googleapis.com/google.protobuf.Timestamp", "value": "1970-01-01T00:02:03Z"});
344        assert_eq!(got, want);
345        Ok(())
346    }
347
348    #[test]
349    fn deserialize_timestamp() -> Result {
350        let input = json!({"@type": "type.googleapis.com/google.protobuf.Timestamp", "value": "1970-01-01T00:02:03Z"});
351        let any = Any(input.as_object().unwrap().clone());
352        assert_eq!(
353            any.type_url(),
354            Some("type.googleapis.com/google.protobuf.Timestamp")
355        );
356        let d = any.try_into_message::<Timestamp>()?;
357        assert_eq!(d, Timestamp::clamp(123, 0));
358        Ok(())
359    }
360
361    #[test]
362    fn serialize_generic() -> Result {
363        let d = Stored {
364            parent: "parent".to_string(),
365            id: "id".to_string(),
366        };
367        let any = Any::try_from(&d)?;
368        assert_eq!(any.type_url(), Some("type.googleapis.com/wkt.test.Stored"));
369        let got = serde_json::to_value(any)?;
370        let want =
371            json!({"@type": "type.googleapis.com/wkt.test.Stored", "parent": "parent", "id": "id"});
372        assert_eq!(got, want);
373        Ok(())
374    }
375
376    #[test]
377    fn deserialize_generic() -> Result {
378        let input =
379            json!({"@type": "type.googleapis.com/wkt.test.Stored", "parent": "parent", "id": "id"});
380        let any = Any(input.as_object().unwrap().clone());
381        assert_eq!(any.type_url(), Some("type.googleapis.com/wkt.test.Stored"));
382        let d = any.try_into_message::<Stored>()?;
383        assert_eq!(
384            d,
385            Stored {
386                parent: "parent".to_string(),
387                id: "id".to_string()
388            }
389        );
390        Ok(())
391    }
392
393    #[derive(Default, serde::Serialize, serde::Deserialize)]
394    struct DetectBadMessages(serde_json::Value);
395    impl crate::message::Message for DetectBadMessages {
396        fn typename() -> &'static str {
397            "not used"
398        }
399    }
400
401    #[test]
402    fn try_from_error() -> Result {
403        let input = DetectBadMessages(json!([2, 3]));
404        let got = Any::try_from(&input);
405        assert!(got.is_err(), "{got:?}");
406
407        Ok(())
408    }
409
410    #[test]
411    fn deserialize_missing_type_field() -> Result {
412        let input = json!({"@type-is-missing": ""});
413        let any = serde_json::from_value::<Any>(input)?;
414        let got = any.try_into_message::<Stored>();
415        assert!(got.is_err());
416        Ok(())
417    }
418
419    #[test]
420    fn deserialize_invalid_type_field() -> Result {
421        let input = json!({"@type": [1, 2, 3]});
422        let any = serde_json::from_value::<Any>(input)?;
423        let got = any.try_into_message::<Stored>();
424        assert!(got.is_err());
425        Ok(())
426    }
427
428    #[test]
429    fn deserialize_missing_value_field() -> Result {
430        let input = json!({"@type": "type.googleapis.com/google.protobuf.Duration", "value-is-missing": "1.2s"});
431        let any = serde_json::from_value::<Any>(input)?;
432        let got = any.try_into_message::<Duration>();
433        assert!(got.is_err());
434        Ok(())
435    }
436
437    #[test]
438    fn deserialize_invalid_value_field() -> Result {
439        let input =
440            json!({"@type": "type.googleapis.com/google.protobuf.Duration", "value": ["1.2s"]});
441        let any = serde_json::from_value::<Any>(input)?;
442        let got = any.try_into_message::<Duration>();
443        assert!(got.is_err());
444        Ok(())
445    }
446
447    #[test]
448    fn deserialize_type_mismatch() -> Result {
449        let input =
450            json!({"@type": "type.googleapis.com/google.protobuf.Duration", "value": "1.2s"});
451        let any = serde_json::from_value::<Any>(input)?;
452        let got = any.try_into_message::<Timestamp>();
453        assert!(got.is_err());
454        let error = got.err().unwrap();
455        assert!(
456            format!("{error}").contains("type.googleapis.com/google.protobuf.Duration"),
457            "{error}"
458        );
459        assert!(
460            format!("{error}").contains("type.googleapis.com/google.protobuf.Timestamp"),
461            "{error}"
462        );
463        Ok(())
464    }
465}