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
15use crate::message::MessageSerializer;
16
17/// `Any` contains an arbitrary serialized protocol buffer message along with a
18/// URL that describes the type of the serialized message.
19///
20/// # Example
21/// ```
22/// # use google_cloud_wkt::{Any, AnyError, Duration, Timestamp};
23/// let duration = Duration::clamp(123, 456);
24/// let any = Any::from_msg(&duration)?;
25/// let extracted = any.to_msg::<Duration>()?;
26/// assert_eq!(extracted, duration);
27/// let fail = any.to_msg::<Timestamp>();
28/// assert!(matches!(fail, Err(AnyError::TypeMismatch{..})));
29/// # Ok::<(), AnyError>(())
30/// ```
31///
32/// Protobuf library provides support to pack/unpack Any values in the form
33/// of utility functions or additional generated methods of the Any type.
34///
35///
36/// # JSON
37///
38/// The JSON representation of an `Any` value uses the regular
39/// representation of the deserialized, embedded message, with an
40/// additional field `@type` which contains the type URL. Example:
41///
42/// ```norust
43///     package google.profile;
44///     message Person {
45///       string first_name = 1;
46///       string last_name = 2;
47///     }
48///
49///     {
50///       "@type": "type.googleapis.com/google.profile.Person",
51///       "firstName": <string>,
52///       "lastName": <string>
53///     }
54/// ```
55///
56/// If the embedded message type is well-known and has a custom JSON
57/// representation, that representation will be embedded adding a field
58/// `value` which holds the custom JSON in addition to the `@type`
59/// field. Example (for message [google.protobuf.Duration][crate::Duration]):
60///
61/// ```norust
62///     {
63///       "@type": "type.googleapis.com/google.protobuf.Duration",
64///       "value": "1.212s"
65///     }
66/// ```
67#[derive(Clone, Debug, Default, PartialEq)]
68#[non_exhaustive]
69pub struct Any(serde_json::Map<String, serde_json::Value>);
70
71/// Indicates a problem trying to use an [Any].
72///
73/// # Example
74/// ```rust
75/// # use google_cloud_wkt::{Any, AnyError, Duration, Timestamp};
76/// use serde_json::json;
77/// let any = serde_json::from_value::<Any>(json!({
78///     "@type": "type.googleapis.com/google.protobuf.Duration",
79///     "value": "123.5" // missing `s` suffix
80/// }))?;
81/// let extracted = any.to_msg::<Duration>();
82/// assert!(matches!(extracted, Err(AnyError::Deserialization(_))));
83/// # Ok::<(), Box<dyn std::error::Error>>(())
84/// ```
85///
86/// # Example
87/// ```rust
88/// # use google_cloud_wkt::{Any, AnyError, Duration, Timestamp};
89/// let duration = Duration::clamp(60, 0);
90/// let any = Any::from_msg(&duration)?;
91/// let extracted = any.to_msg::<Timestamp>();
92/// assert!(matches!(extracted, Err(AnyError::TypeMismatch{..})));
93/// # Ok::<(), AnyError>(())
94/// ```
95#[derive(thiserror::Error, Debug)]
96#[non_exhaustive]
97pub enum AnyError {
98    /// Problem serializing an object into an [Any].
99    #[error("cannot serialize object into an Any, source={0}")]
100    Serialization(#[source] BoxedError),
101
102    /// Problem deserializing an object from an [Any].
103    #[error("cannot deserialize from an Any, source={0}")]
104    Deserialization(#[source] BoxedError),
105
106    /// Mismatched type, the [Any] does not contain the desired type.
107    #[error(
108        "mismatched typenames extracting from Any, the any has {has}, the target type is {want}"
109    )]
110    TypeMismatch {
111        /// The type URL contained in the `Any`.
112        has: String,
113        /// The type URL of the desired type to extract from the `Any`.
114        want: String,
115    },
116}
117
118impl AnyError {
119    pub(crate) fn ser<T: Into<BoxedError>>(v: T) -> Self {
120        Self::Serialization(v.into())
121    }
122
123    pub(crate) fn deser<T: Into<BoxedError>>(v: T) -> Self {
124        Self::Deserialization(v.into())
125    }
126
127    pub(crate) fn mismatch(has: &str, want: &str) -> Self {
128        Self::TypeMismatch {
129            has: has.into(),
130            want: want.into(),
131        }
132    }
133}
134
135type BoxedError = Box<dyn std::error::Error + Send + Sync>;
136type Error = AnyError;
137
138impl Any {
139    /// Returns the name of the contained type.
140    ///
141    /// # Example
142    /// ```
143    /// # use google_cloud_wkt::{Any, AnyError, Duration, Timestamp};
144    /// use google_cloud_wkt::message::Message;
145    /// let any = Any::from_msg(&Duration::clamp(123, 456))?;
146    /// assert_eq!(any.type_url(), Some(Duration::typename()));
147    /// # Ok::<(), AnyError>(())
148    /// ```
149    ///
150    /// An `Any` may contain any message type. The name of the message is a URL,
151    /// usually with the `https://` scheme elided. All types in Google Cloud
152    /// APIs are of the form `type.googleapis.com/${fully-qualified-name}`.
153    ///
154    /// Note that this is not an available URL where you can download data (such
155    /// as the message schema) from.
156    ///
157    pub fn type_url(&self) -> Option<&str> {
158        self.0.get("@type").and_then(serde_json::Value::as_str)
159    }
160
161    /// Creates a new [Any] from any [Message][crate::message::Message] that
162    /// also supports serialization to JSON.
163    ///
164    /// # Example
165    /// ```
166    /// # use google_cloud_wkt::{Any, AnyError, Duration, Timestamp};
167    /// let any = Any::from_msg(&Duration::clamp(123, 456))?;
168    /// # Ok::<(), AnyError>(())
169    /// ```
170    pub fn from_msg<T>(message: &T) -> Result<Self, Error>
171    where
172        T: crate::message::Message,
173    {
174        let serializer = T::serializer();
175        let value = serializer.serialize_to_map(message)?;
176        Ok(Any(value))
177    }
178
179    /// Extracts (if possible) a `T` value from the [Any].
180    ///
181    /// # Example
182    /// ```
183    /// # use google_cloud_wkt::{Any, AnyError, Duration, Timestamp};
184    /// let any = Any::from_msg(&Duration::clamp(123, 456))?;
185    /// let duration = any.to_msg::<Duration>()?;
186    /// assert_eq!(duration, Duration::clamp(123, 456));
187    /// # Ok::<(), AnyError>(())
188    /// ```
189    pub fn to_msg<T>(&self) -> Result<T, Error>
190    where
191        T: crate::message::Message,
192    {
193        let map = &self.0;
194        let r#type = map
195            .get("@type")
196            .and_then(|v| v.as_str())
197            .ok_or_else(|| "@type field is missing or is not a string".to_string())
198            .map_err(Error::deser)?;
199        Self::check_typename(r#type, T::typename())?;
200
201        let serializer = T::serializer();
202        serializer.deserialize_from_map(map)
203    }
204
205    fn check_typename(has: &str, want: &str) -> Result<(), Error> {
206        if has == want {
207            return Ok(());
208        }
209        Err(Error::mismatch(has, want))
210    }
211}
212
213impl crate::message::Message for Any {
214    fn typename() -> &'static str {
215        "type.googleapis.com/google.protobuf.Any"
216    }
217
218    #[allow(private_interfaces)]
219    fn serializer() -> impl crate::message::MessageSerializer<Self> {
220        crate::message::ValueSerializer::<Self>::new()
221    }
222}
223
224/// Implement [`serde`](::serde) serialization for [Any].
225#[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
226impl serde::ser::Serialize for Any {
227    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
228    where
229        S: serde::ser::Serializer,
230    {
231        self.0.serialize(serializer)
232    }
233}
234
235use serde::de::Unexpected;
236type ValueMap = serde_json::Map<String, serde_json::Value>;
237
238const EXPECTED: &str = "a valid type URL string in the @type field";
239
240/// Implement [`serde`](::serde) deserialization for [Any].
241#[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
242impl<'de> serde::de::Deserialize<'de> for Any {
243    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
244    where
245        D: serde::Deserializer<'de>,
246    {
247        use serde::de::Error as _;
248        use serde_json::Value;
249        let value = ValueMap::deserialize(deserializer)?;
250        match value.get("@type") {
251            None => Ok(Any(value)),
252            Some(Value::String(s)) if validate_type_url(s) => Ok(Any(value)),
253            Some(Value::String(s)) => Err(D::Error::invalid_value(Unexpected::Str(s), &EXPECTED)),
254            Some(Value::Null) => Err(type_field_invalid_type("JSON null")),
255            Some(Value::Object(_)) => Err(type_field_invalid_type("JSON object")),
256            Some(Value::Array(_)) => Err(type_field_invalid_type("JSON array")),
257            Some(Value::Number(_)) => Err(type_field_invalid_type("JSON number")),
258            Some(Value::Bool(_)) => Err(type_field_invalid_type("JSON boolean")),
259        }
260    }
261}
262
263fn type_field_invalid_type<E>(reason: &str) -> E
264where
265    E: serde::de::Error,
266{
267    E::invalid_type(Unexpected::Other(reason), &EXPECTED)
268}
269
270fn validate_type_url(type_url: &str) -> bool {
271    match type_url.split_once("/") {
272        None => false,
273        Some((host, path)) => is_host(host) && is_protobuf_id(path),
274    }
275}
276
277fn is_host(host: &str) -> bool {
278    if host == "type.googleapis.com" {
279        return true;
280    }
281    if host.contains("_") {
282        return false;
283    }
284    // Slow path, should not happen very often.
285    url::Url::parse(format!("https://{host}").as_str()).is_ok()
286}
287
288fn is_protobuf_id(path: &str) -> bool {
289    path.split(".").all(is_identifier)
290}
291
292fn is_identifier(id: &str) -> bool {
293    !id.is_empty()
294        && id.chars().all(|c: char| c.is_alphanumeric() || c == '_')
295        && !id.starts_with(|c: char| c.is_ascii_digit())
296}
297
298#[cfg(test)]
299mod tests {
300    use super::*;
301    use crate::duration::*;
302    use crate::empty::Empty;
303    use crate::field_mask::*;
304    use crate::timestamp::*;
305    use serde_json::{Value, json};
306    use test_case::test_case;
307
308    type Result = anyhow::Result<()>;
309
310    #[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)]
311    #[serde(rename_all = "camelCase")]
312    #[non_exhaustive]
313    pub struct Stored {
314        #[serde(skip_serializing_if = "String::is_empty")]
315        pub parent: String,
316        #[serde(skip_serializing_if = "String::is_empty")]
317        pub id: String,
318    }
319
320    impl crate::message::Message for Stored {
321        fn typename() -> &'static str {
322            "type.googleapis.com/wkt.test.Stored"
323        }
324    }
325
326    #[test]
327    fn serialize_any() -> Result {
328        let d = Duration::clamp(60, 0);
329        let any = Any::from_msg(&d)?;
330        let any = Any::from_msg(&any)?;
331        assert_eq!(
332            any.type_url(),
333            Some("type.googleapis.com/google.protobuf.Any")
334        );
335        let got = serde_json::to_value(any)?;
336        let want = json!({
337            "@type": "type.googleapis.com/google.protobuf.Any",
338            "value": {
339                "@type": "type.googleapis.com/google.protobuf.Duration",
340                "value": "60s"
341            }
342        });
343        assert_eq!(got, want);
344        Ok(())
345    }
346
347    #[test]
348    fn deserialize_any() -> Result {
349        let input = json!({
350            "@type": "type.googleapis.com/google.protobuf.Any",
351            "value": {
352                "@type": "type.googleapis.com/google.protobuf.Duration",
353                "value": "60s"
354            }
355        });
356        let any = Any(input.as_object().unwrap().clone());
357        assert_eq!(
358            any.type_url(),
359            Some("type.googleapis.com/google.protobuf.Any")
360        );
361        let any = any.to_msg::<Any>()?;
362        assert_eq!(
363            any.type_url(),
364            Some("type.googleapis.com/google.protobuf.Duration")
365        );
366        let d = any.to_msg::<Duration>()?;
367        assert_eq!(d, Duration::clamp(60, 0));
368        Ok(())
369    }
370
371    #[test_case(json!({"value": "7"}))]
372    #[test_case(json!({"@type": "type.googleapis.com/foo"}))]
373    #[test_case(json!({"@type": "type.googleapis.com/foo_bar"}))]
374    #[test_case(json!({"@type": "type.googleapis.com/foo_bar.baz"}))]
375    #[test_case(json!({"@type": "type.googleapis.com/foo_bar.baz.Message"}))]
376    #[test_case(json!({"@type": "type.googleapis.com/foo_bar.baz.Message3"}))]
377    #[test_case(json!({"@type": "type.googleapis.com/foo2_bar.baz.Message3"}))]
378    fn deserialize_any_success(input: Value) {
379        let any = serde_json::from_value::<Any>(input.clone());
380        assert!(any.is_ok(), "{any:?} from {input:?}");
381    }
382
383    #[test_case(json!({"@type": "", "value": "7"}))]
384    #[test_case(json!({"@type": "type.googleapis.com/", "value": "7"}))]
385    #[test_case(json!({"@type": "/google.protobuf.Duration", "value": "7"}))]
386    #[test_case(json!({"@type": "type.googleapis.com/google.protobuf.7abc", "value": "7"}))]
387    #[test_case(json!({"@type": "type.googlea_pis.com/google.protobuf.Duration", "value": "7"}))]
388    #[test_case(json!({"@type": "abc_123/google.protobuf.Foo", "value": "7"}))]
389    #[test_case(json!({"@type": [], "value": "7"}); "type is array")]
390    #[test_case(json!({"@type": 7, "value": "7"}))]
391    #[test_case(json!({"@type": true, "value": "7"}))]
392    #[test_case(json!({"@type": null, "value": "7"}))]
393    #[test_case(json!({"@type": {}, "value": "7"}); "type is object")]
394    fn deserialize_bad_types(input: Value) {
395        let err = serde_json::from_value::<Any>(input).expect_err("should fail");
396        assert!(err.is_data(), "{err:?}");
397    }
398
399    #[test]
400    fn serialize_duration() -> Result {
401        let d = Duration::clamp(60, 0);
402        let any = Any::from_msg(&d)?;
403        assert_eq!(
404            any.type_url(),
405            Some("type.googleapis.com/google.protobuf.Duration")
406        );
407        let got = serde_json::to_value(any)?;
408        let want = json!({"@type": "type.googleapis.com/google.protobuf.Duration", "value": "60s"});
409        assert_eq!(got, want);
410        Ok(())
411    }
412
413    #[test]
414    fn deserialize_duration() -> Result {
415        let input =
416            json!({"@type": "type.googleapis.com/google.protobuf.Duration", "value": "60s"});
417        let any = Any(input.as_object().unwrap().clone());
418        assert_eq!(
419            any.type_url(),
420            Some("type.googleapis.com/google.protobuf.Duration")
421        );
422        let d = any.to_msg::<Duration>()?;
423        assert_eq!(d, Duration::clamp(60, 0));
424        Ok(())
425    }
426
427    #[test]
428    fn serialize_empty() -> Result {
429        let empty = Empty::default();
430        let any = Any::from_msg(&empty)?;
431        assert_eq!(
432            any.type_url(),
433            Some("type.googleapis.com/google.protobuf.Empty")
434        );
435        let got = serde_json::to_value(any)?;
436        let want = json!({"@type": "type.googleapis.com/google.protobuf.Empty"});
437        assert_eq!(got, want);
438        Ok(())
439    }
440
441    #[test]
442    fn deserialize_empty() -> Result {
443        let input = json!({"@type": "type.googleapis.com/google.protobuf.Empty"});
444        let any = Any(input.as_object().unwrap().clone());
445        assert_eq!(
446            any.type_url(),
447            Some("type.googleapis.com/google.protobuf.Empty")
448        );
449        let empty = any.to_msg::<Empty>()?;
450        assert_eq!(empty, Empty::default());
451        Ok(())
452    }
453
454    #[test]
455    fn serialize_field_mask() -> Result {
456        let d = FieldMask::default().set_paths(["a", "b"].map(str::to_string).to_vec());
457        let any = Any::from_msg(&d)?;
458        assert_eq!(
459            any.type_url(),
460            Some("type.googleapis.com/google.protobuf.FieldMask")
461        );
462        let got = serde_json::to_value(any)?;
463        let want =
464            json!({"@type": "type.googleapis.com/google.protobuf.FieldMask", "value": "a,b"});
465        assert_eq!(got, want);
466        Ok(())
467    }
468
469    #[test]
470    fn deserialize_field_mask() -> Result {
471        let input =
472            json!({"@type": "type.googleapis.com/google.protobuf.FieldMask", "value": "a,b"});
473        let any = Any(input.as_object().unwrap().clone());
474        assert_eq!(
475            any.type_url(),
476            Some("type.googleapis.com/google.protobuf.FieldMask")
477        );
478        let d = any.to_msg::<FieldMask>()?;
479        assert_eq!(
480            d,
481            FieldMask::default().set_paths(["a", "b"].map(str::to_string).to_vec())
482        );
483        Ok(())
484    }
485
486    #[test]
487    fn serialize_timestamp() -> Result {
488        let d = Timestamp::clamp(123, 0);
489        let any = Any::from_msg(&d)?;
490        assert_eq!(
491            any.type_url(),
492            Some("type.googleapis.com/google.protobuf.Timestamp")
493        );
494        let got = serde_json::to_value(any)?;
495        let want = json!({"@type": "type.googleapis.com/google.protobuf.Timestamp", "value": "1970-01-01T00:02:03Z"});
496        assert_eq!(got, want);
497        Ok(())
498    }
499
500    #[test]
501    fn deserialize_timestamp() -> Result {
502        let input = json!({"@type": "type.googleapis.com/google.protobuf.Timestamp", "value": "1970-01-01T00:02:03Z"});
503        let any = Any(input.as_object().unwrap().clone());
504        assert_eq!(
505            any.type_url(),
506            Some("type.googleapis.com/google.protobuf.Timestamp")
507        );
508        let d = any.to_msg::<Timestamp>()?;
509        assert_eq!(d, Timestamp::clamp(123, 0));
510        Ok(())
511    }
512
513    #[test]
514    fn serialize_generic() -> Result {
515        let d = Stored {
516            parent: "parent".to_string(),
517            id: "id".to_string(),
518        };
519        let any = Any::from_msg(&d)?;
520        assert_eq!(any.type_url(), Some("type.googleapis.com/wkt.test.Stored"));
521        let got = serde_json::to_value(any)?;
522        let want =
523            json!({"@type": "type.googleapis.com/wkt.test.Stored", "parent": "parent", "id": "id"});
524        assert_eq!(got, want);
525        Ok(())
526    }
527
528    #[test]
529    fn deserialize_generic() -> Result {
530        let input =
531            json!({"@type": "type.googleapis.com/wkt.test.Stored", "parent": "parent", "id": "id"});
532        let any = Any(input.as_object().unwrap().clone());
533        assert_eq!(any.type_url(), Some("type.googleapis.com/wkt.test.Stored"));
534        let d = any.to_msg::<Stored>()?;
535        assert_eq!(
536            d,
537            Stored {
538                parent: "parent".to_string(),
539                id: "id".to_string()
540            }
541        );
542        Ok(())
543    }
544
545    #[derive(Default, serde::Serialize, serde::Deserialize)]
546    struct DetectBadMessages(serde_json::Value);
547    impl crate::message::Message for DetectBadMessages {
548        fn typename() -> &'static str {
549            "not used"
550        }
551    }
552
553    #[test]
554    fn try_from_error() -> Result {
555        let input = DetectBadMessages(json!([2, 3]));
556        let got = Any::from_msg(&input);
557        assert!(got.is_err(), "{got:?}");
558
559        Ok(())
560    }
561
562    #[test]
563    fn deserialize_missing_value_field() -> Result {
564        let input = json!({"@type": "type.googleapis.com/google.protobuf.Duration", "value-is-missing": "1.2s"});
565        let any = serde_json::from_value::<Any>(input)?;
566        let got = any.to_msg::<Duration>();
567        assert!(got.is_err());
568        Ok(())
569    }
570
571    #[test]
572    fn deserialize_invalid_value_field() -> Result {
573        let input =
574            json!({"@type": "type.googleapis.com/google.protobuf.Duration", "value": ["1.2s"]});
575        let any = serde_json::from_value::<Any>(input)?;
576        let got = any.to_msg::<Duration>();
577        assert!(got.is_err());
578        Ok(())
579    }
580
581    #[test]
582    fn deserialize_type_mismatch() -> Result {
583        let input =
584            json!({"@type": "type.googleapis.com/google.protobuf.Duration", "value": "1.2s"});
585        let any = serde_json::from_value::<Any>(input)?;
586        let got = any.to_msg::<Timestamp>();
587        assert!(got.is_err());
588        let error = got.err().unwrap();
589        assert!(
590            format!("{error}").contains("type.googleapis.com/google.protobuf.Duration"),
591            "{error}"
592        );
593        assert!(
594            format!("{error}").contains("type.googleapis.com/google.protobuf.Timestamp"),
595            "{error}"
596        );
597        Ok(())
598    }
599}