google_cloud_wkt/
message.rs

1// Copyright 2025 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//! Define traits required of all messages.
16
17pub(crate) type Map = serde_json::Map<String, serde_json::Value>;
18use crate::AnyError as Error;
19
20/// A trait that must be implemented by all messages.
21///
22/// Messages sent to and received from Google Cloud services may be wrapped in
23/// [Any][crate::any::Any]. `Any` uses a `@type` field to encoding the type
24/// name and then validates extraction and insertion against this type.
25pub trait Message {
26    /// The typename of this message.
27    fn typename() -> &'static str;
28
29    /// Store the value into a JSON object.
30    fn to_map(&self) -> Result<Map, Error>
31    where
32        Self: serde::ser::Serialize + Sized,
33    {
34        to_json_object(self)
35    }
36
37    /// Extract the value from a JSON object.
38    fn from_map(map: &Map) -> Result<Self, Error>
39    where
40        Self: serde::de::DeserializeOwned,
41    {
42        let map: Map = map
43            .iter()
44            .filter_map(|(k, v)| {
45                if k == "@type" {
46                    return None;
47                }
48                Some((k.clone(), v.clone()))
49            })
50            .collect();
51        serde_json::from_value::<Self>(serde_json::Value::Object(map)).map_err(Error::deser)
52    }
53}
54
55pub(crate) fn to_json_object<T>(message: &T) -> Result<Map, Error>
56where
57    T: Message + serde::ser::Serialize,
58{
59    use serde_json::Value;
60
61    let value = serde_json::to_value(message).map_err(Error::ser)?;
62    match value {
63        Value::Object(mut map) => {
64            map.insert(
65                "@type".to_string(),
66                Value::String(T::typename().to_string()),
67            );
68            Ok(map)
69        }
70        _ => Err(unexpected_json_type()),
71    }
72}
73
74pub(crate) fn to_json_string<T>(message: &T) -> Result<Map, Error>
75where
76    T: Message + serde::ser::Serialize,
77{
78    use serde_json::Value;
79    let value = serde_json::to_value(message).map_err(Error::ser)?;
80    match value {
81        Value::String(s) => {
82            // Only a few well-known messages are serialized into something
83            // other than a object. In all cases, they are serialized using
84            // a small JSON object, with the string in the `value` field.
85            let map: Map = [("@type", T::typename().to_string()), ("value", s)]
86                .into_iter()
87                .map(|(k, v)| (k.to_string(), Value::String(v)))
88                .collect();
89            Ok(map)
90        }
91        _ => Err(unexpected_json_type()),
92    }
93}
94
95pub(crate) fn from_value<T>(map: &Map) -> Result<T, Error>
96where
97    T: serde::de::DeserializeOwned,
98{
99    map.get("value")
100        .map(|v| serde_json::from_value::<T>(v.clone()))
101        .ok_or_else(missing_value_field)?
102        .map_err(Error::deser)
103}
104
105pub(crate) fn missing_value_field() -> Error {
106    Error::deser("value field is missing")
107}
108
109fn unexpected_json_type() -> Error {
110    Error::ser("unexpected JSON type, only Object and String are supported")
111}
112
113#[cfg(test)]
114mod test {
115    use super::*;
116    use serde_json::json;
117
118    #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
119    struct TestMessage {
120        #[serde(flatten)]
121        _unknown_fields: serde_json::Map<String, serde_json::Value>,
122    }
123
124    impl Message for TestMessage {
125        fn typename() -> &'static str {
126            "TestMessage"
127        }
128    }
129
130    #[test]
131    fn drop_type_field() {
132        let input = json!({
133            "@type": "TestMessage",
134            "a": 1,
135            "b": 2,
136        });
137        let map = input.as_object().cloned().unwrap();
138        let test = TestMessage::from_map(&map).unwrap();
139        assert!(test._unknown_fields.get("@type").is_none(), "{test:?}");
140    }
141}