google_cloud_wkt/
wrappers.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 base64::{Engine, engine::general_purpose::STANDARD};
16
17/// Implements the `google.cloud.DoubleValue` well-known type.
18///
19/// In early versions of the `proto3` syntax optional primitive types were
20/// represented by well-known messages, with a single field, that contained the
21/// value. In Rust, we represent these with `Option` of the correct type. The
22/// aliases are introduced here to simplify the code generator and resolve any
23/// references in code or documentation.
24///
25/// The JSON representation for `DoubleValue` is JSON number.
26pub type DoubleValue = f64;
27
28/// Implements the `google.cloud.FloatValue` well-known type.
29///
30/// In early versions of the `proto3` syntax optional primitive types were
31/// represented by well-known messages, with a single field, that contained the
32/// value. In Rust, we represent these with `Option` of the correct type. The
33/// aliases are introduced here to simplify the code generator and resolve any
34/// references in code or documentation.
35///
36/// The JSON representation for `FloatValue` is JSON number.
37pub type FloatValue = f32;
38
39/// Implements the `google.cloud.Int64Value` well-known type.
40///
41/// In early versions of the `proto3` syntax optional primitive types were
42/// represented by well-known messages, with a single field, that contained the
43/// value. In Rust, we represent these with `Option` of the correct type. The
44/// aliases are introduced here to simplify the code generator and resolve any
45/// references in code or documentation.
46///
47/// The JSON representation for `Int64Value` is JSON string.
48pub type Int64Value = i64;
49
50/// Implements the `google.cloud.UInt64Value` well-known type.
51///
52/// In early versions of the `proto3` syntax optional primitive types were
53/// represented by well-known messages, with a single field, that contained the
54/// value. In Rust, we represent these with `Option` of the correct type. The
55/// aliases are introduced here to simplify the code generator and resolve any
56/// references in code or documentation.
57///
58/// The JSON representation for `UInt64Value` is JSON string.
59pub type UInt64Value = u64;
60
61/// Implements the `google.cloud.Int32Value` well-known type.
62///
63/// In early versions of the `proto3` syntax optional primitive types were
64/// represented by well-known messages, with a single field, that contained the
65/// value. In Rust, we represent these with `Option` of the correct type. The
66/// aliases are introduced here to simplify the code generator and resolve any
67/// references in code or documentation.
68///
69/// The JSON representation for `Int32Value` is JSON number.
70pub type Int32Value = i32;
71
72/// Implements the `google.cloud.UInt32Value` well-known type.
73///
74/// In early versions of the `proto3` syntax optional primitive types were
75/// represented by well-known messages, with a single field, that contained the
76/// value. In Rust, we represent these with `Option` of the correct type. The
77/// aliases are introduced here to simplify the code generator and resolve any
78/// references in code or documentation.
79///
80/// The JSON representation for `UInt32Value` is JSON number.
81pub type UInt32Value = u32;
82
83/// Implements the `google.cloud.BoolValue` well-known type.
84///
85/// In early versions of the `proto3` syntax optional primitive types were
86/// represented by well-known messages, with a single field, that contained the
87/// value. In Rust, we represent these with `Option` of the correct type. The
88/// aliases are introduced here to simplify the code generator and resolve any
89/// references in code or documentation.
90///
91/// The JSON representation for `BoolValue` is JSON `true` and `false`.
92pub type BoolValue = bool;
93
94/// Implements the `google.cloud.StringValue` well-known type.
95///
96/// In early versions of the `proto3` syntax optional primitive types were
97/// represented by well-known messages, with a single field, that contained the
98/// value. In Rust, we represent these with `Option` of the correct type. The
99/// aliases are introduced here to simplify the code generator and resolve any
100/// references in code or documentation.
101///
102/// The JSON representation for `StringValue` is JSON string.
103pub type StringValue = String;
104
105/// Implements the `google.cloud.BytesValue` well-known type.
106///
107/// In early versions of the `proto3` syntax optional primitive types were
108/// represented by well-known messages, with a single field, that contained the
109/// value. In Rust, we represent these with `Option` of the correct type. The
110/// aliases are introduced here to simplify the code generator and resolve any
111/// references in code or documentation.
112///
113/// The JSON representation for `BytesValue` is JSON string.
114pub type BytesValue = bytes::Bytes;
115
116macro_rules! impl_message {
117    ($t: ty) => {
118        impl crate::message::Message for $t {
119            fn typename() -> &'static str {
120                concat!("type.googleapis.com/google.protobuf.", stringify!($t))
121            }
122            fn to_map(&self) -> Result<crate::message::Map, crate::AnyError>
123            where
124                Self: serde::ser::Serialize + Sized,
125            {
126                let map: crate::message::Map = [
127                    (
128                        "@type",
129                        serde_json::Value::String(Self::typename().to_string()),
130                    ),
131                    ("value", serde_json::json!(self)),
132                ]
133                .into_iter()
134                .map(|(k, v)| (k.to_string(), v))
135                .collect();
136                Ok(map)
137            }
138            fn from_map(map: &crate::message::Map) -> Result<Self, crate::AnyError>
139            where
140                Self: serde::de::DeserializeOwned,
141            {
142                crate::message::from_value::<Self>(map)
143            }
144        }
145    };
146}
147
148impl_message!(DoubleValue);
149impl_message!(FloatValue);
150impl_message!(Int32Value);
151impl_message!(UInt32Value);
152impl_message!(BoolValue);
153impl_message!(StringValue);
154
155fn encode_string<T>(value: String) -> Result<crate::message::Map, crate::AnyError>
156where
157    T: crate::message::Message,
158{
159    let map: crate::message::Map = [
160        (
161            "@type",
162            serde_json::Value::String(T::typename().to_string()),
163        ),
164        ("value", serde_json::Value::String(value)),
165    ]
166    .into_iter()
167    .map(|(k, v)| (k.to_string(), v))
168    .collect();
169    Ok(map)
170}
171
172impl crate::message::Message for UInt64Value {
173    fn typename() -> &'static str {
174        "type.googleapis.com/google.protobuf.UInt64Value"
175    }
176    fn to_map(&self) -> Result<crate::message::Map, crate::AnyError>
177    where
178        Self: serde::ser::Serialize + Sized,
179    {
180        encode_string::<Self>(self.to_string())
181    }
182    fn from_map(map: &crate::message::Map) -> Result<Self, crate::AnyError>
183    where
184        Self: serde::de::DeserializeOwned,
185    {
186        map.get("value")
187            .ok_or_else(crate::message::missing_value_field)?
188            .as_str()
189            .ok_or_else(expected_string_value)?
190            .parse::<UInt64Value>()
191            .map_err(crate::AnyError::deser)
192    }
193}
194
195impl crate::message::Message for Int64Value {
196    fn typename() -> &'static str {
197        "type.googleapis.com/google.protobuf.Int64Value"
198    }
199    fn to_map(&self) -> Result<crate::message::Map, crate::AnyError>
200    where
201        Self: serde::ser::Serialize + Sized,
202    {
203        encode_string::<Self>(self.to_string())
204    }
205    fn from_map(map: &crate::message::Map) -> Result<Self, crate::AnyError>
206    where
207        Self: serde::de::DeserializeOwned,
208    {
209        map.get("value")
210            .ok_or_else(crate::message::missing_value_field)?
211            .as_str()
212            .ok_or_else(expected_string_value)?
213            .parse::<Int64Value>()
214            .map_err(crate::AnyError::deser)
215    }
216}
217
218impl crate::message::Message for BytesValue {
219    fn typename() -> &'static str {
220        "type.googleapis.com/google.protobuf.BytesValue"
221    }
222    fn to_map(&self) -> Result<crate::message::Map, crate::AnyError>
223    where
224        Self: serde::ser::Serialize + Sized,
225    {
226        encode_string::<Self>(STANDARD.encode(self))
227    }
228    fn from_map(map: &crate::message::Map) -> Result<Self, crate::AnyError>
229    where
230        Self: serde::de::DeserializeOwned,
231    {
232        let s = map
233            .get("value")
234            .ok_or_else(crate::message::missing_value_field)?
235            .as_str()
236            .ok_or_else(expected_string_value)?;
237        STANDARD
238            .decode(s)
239            .map(BytesValue::from)
240            .map_err(crate::AnyError::deser)
241    }
242}
243
244fn expected_string_value() -> crate::AnyError {
245    crate::AnyError::deser("expected value field to be a string")
246}
247
248#[cfg(test)]
249mod test {
250    use super::*;
251    use crate::Any;
252    use crate::message::Message;
253    type Result = std::result::Result<(), Box<dyn std::error::Error>>;
254    use test_case::test_case;
255
256    // Generated with: `echo -n 'Hello, World!' | base64`
257    const HELLO_WORLD_BASE64: &str = "SGVsbG8sIFdvcmxkIQ==";
258
259    #[test_case(1234.5 as DoubleValue, 1234.5, "DoubleValue")]
260    #[test_case(9876.5 as FloatValue, 9876.5, "FloatValue")]
261    #[test_case(-123 as Int64Value, "-123", "Int64Value")]
262    #[test_case(123 as UInt64Value, "123", "UInt64Value")]
263    #[test_case(-123 as Int32Value, -123, "Int32Value")]
264    #[test_case(123 as UInt32Value, 123, "UInt32Value")]
265    #[test_case(true as BoolValue, true, "BoolValue")]
266    #[test_case(StringValue::from("Hello, World!"), "Hello, World!", "StringValue")]
267    #[test_case(BytesValue::from("Hello, World!"), HELLO_WORLD_BASE64, "BytesValue")]
268    fn test_wrapper_in_any<I, V>(input: I, value: V, typename: &str) -> Result
269    where
270        I: crate::message::Message
271            + std::fmt::Debug
272            + PartialEq
273            + serde::de::DeserializeOwned
274            + serde::ser::Serialize,
275        V: serde::ser::Serialize,
276    {
277        let any = Any::try_from(&input)?;
278        let got = serde_json::to_value(&any)?;
279        let want = serde_json::json!({
280            "@type": format!("type.googleapis.com/google.protobuf.{}", typename),
281            "value": value,
282        });
283        assert_eq!(got, want);
284        let output = any.try_into_message::<I>()?;
285        assert_eq!(output, input);
286        Ok(())
287    }
288
289    #[test_case(Int32Value::default(), DoubleValue::default())]
290    #[test_case(Int32Value::default(), FloatValue::default())]
291    #[test_case(DoubleValue::default(), Int64Value::default())]
292    #[test_case(DoubleValue::default(), UInt64Value::default())]
293    #[test_case(DoubleValue::default(), Int32Value::default())]
294    #[test_case(DoubleValue::default(), UInt32Value::default())]
295    #[test_case(DoubleValue::default(), BoolValue::default())]
296    #[test_case(DoubleValue::default(), StringValue::default())]
297    #[test_case(DoubleValue::default(), BytesValue::default())]
298    fn test_wrapper_in_any_with_bad_typenames<T, U>(from: T, _into: U) -> Result
299    where
300        T: Message + std::fmt::Debug + serde::ser::Serialize,
301        U: Message + std::fmt::Debug + serde::de::DeserializeOwned,
302    {
303        let any = Any::try_from(&from)?;
304        assert!(any.try_into_message::<U>().is_err());
305        Ok(())
306    }
307
308    #[test_case(Int64Value::default(), "Int64Value")]
309    #[test_case(UInt64Value::default(), "UInt64Value")]
310    fn test_wrapper_bad_encoding<T>(_input: T, typename: &str) -> Result
311    where
312        T: Message + std::fmt::Debug + serde::ser::Serialize + serde::de::DeserializeOwned,
313    {
314        let map = serde_json::json!({
315            "@type": format!("type.googleapis.com/google.protobuf.{}", typename),
316            "value": 0,
317        });
318        let e = T::from_map(map.as_object().unwrap());
319        assert!(e.is_err());
320        let fmt = format!("{:?}", e);
321        assert!(fmt.contains("expected value field to be a string"), "{fmt}");
322        Ok(())
323    }
324
325    #[test]
326    fn test_wrapper_bad_encoding_base64() -> Result {
327        let map = serde_json::json!({
328            "@type": "type.googleapis.com/google.protobuf.BytesValue",
329            "value": "Oops, I forgot to base64 encode this.",
330        });
331        assert!(BytesValue::from_map(map.as_object().unwrap()).is_err());
332        Ok(())
333    }
334}