Skip to main content

buffa_types/
view_serde_ext.rs

1//! Hand-written `serde::Serialize` impls for well-known type **view** types.
2//!
3//! The owned WKT structs ([`Timestamp`](crate::Timestamp),
4//! [`Duration`](crate::Duration), [`Any`](crate::Any), …) carry hand-written
5//! `Serialize`/`Deserialize` impls in their respective `*_ext` modules because
6//! protobuf's JSON mapping treats them specially (RFC 3339 timestamps, `"1.5s"`
7//! durations, `@type`-flattened `Any`, raw JSON values, …).  The codegen
8//! cannot emit these impls automatically.
9//!
10//! When views and JSON are both enabled in `buffa-build`, generated view types
11//! reference WKT view types (`TimestampView<'_>`, …) directly, so those WKT
12//! views also need `Serialize`.  These impls delegate to the owned type via
13//! [`MessageView::to_owned_message`](buffa::MessageView::to_owned_message),
14//! trading a per-WKT-field allocation for parity with the owned proto3 JSON
15//! encoding.  The rest of the parent message stays zero-copy.
16//!
17//! For the flat WKTs ([`Timestamp`](crate::Timestamp),
18//! [`Duration`](crate::Duration), [`FieldMask`](crate::FieldMask), the
19//! wrappers, [`Empty`](crate::Empty)) the allocation is a few words.  For
20//! [`Struct`](crate::Struct) / [`Value`](crate::Value) /
21//! [`ListValue`](crate::ListValue) / [`Any`](crate::Any) the entire owned
22//! tree is materialized before serde sees it — large nested `Struct` payloads
23//! lose the zero-copy benefit on the serialize path.  Hand-rolling those four
24//! impls to walk the view directly would close the gap; tracked as a
25//! follow-up on the view JSON issue.
26//!
27//! `Deserialize` is intentionally not implemented: view types borrow from a
28//! source buffer and cannot be constructed from arbitrary JSON.
29
30use buffa::MessageView;
31
32use crate::google::protobuf::__buffa::view::{
33    AnyView, BoolValueView, BytesValueView, DoubleValueView, DurationView, EmptyView,
34    FieldMaskView, FloatValueView, Int32ValueView, Int64ValueView, ListValueView, StringValueView,
35    StructView, TimestampView, UInt32ValueView, UInt64ValueView, ValueView,
36};
37
38/// Implement `serde::Serialize` for a WKT view by delegating to the owned form.
39macro_rules! wkt_view_serialize {
40    ($($view:ident),+ $(,)?) => {
41        $(
42            impl serde::Serialize for $view<'_> {
43                /// Serializes by converting to the owned WKT and delegating to
44                /// its proto3-JSON `Serialize` impl.  Allocates the owned form
45                /// for this field only; the parent message stays zero-copy.
46                fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
47                    self.to_owned_message()
48                        .map_err(serde::ser::Error::custom)?
49                        .serialize(s)
50                }
51            }
52        )+
53    };
54}
55
56wkt_view_serialize!(
57    AnyView,
58    BoolValueView,
59    BytesValueView,
60    DoubleValueView,
61    DurationView,
62    EmptyView,
63    FieldMaskView,
64    FloatValueView,
65    Int32ValueView,
66    Int64ValueView,
67    ListValueView,
68    StringValueView,
69    StructView,
70    TimestampView,
71    UInt32ValueView,
72    UInt64ValueView,
73    ValueView,
74);
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use crate::google::protobuf::{
80        value::Kind, BoolValue, BytesValue, DoubleValue, Duration, Empty, FieldMask, FloatValue,
81        Int32Value, Int64Value, ListValue, StringValue, Struct, Timestamp, UInt32Value,
82        UInt64Value, Value,
83    };
84    use buffa::Message;
85
86    /// Encode `$owned`, decode it as `$view`, serialize both to JSON, and
87    /// assert the outputs match.  Returns the view's JSON string for
88    /// follow-on assertions.
89    macro_rules! assert_view_json_parity {
90        ($view:ty, $owned:expr) => {{
91            let owned = $owned;
92            let bytes = owned.encode_to_vec();
93            let view = <$view>::decode_view(&bytes).expect("decode_view");
94            let json_owned = serde_json::to_string(&owned).expect("serialize owned");
95            let json_view = serde_json::to_string(&view).expect("serialize view");
96            assert_eq!(json_view, json_owned, "view JSON must match owned JSON");
97            json_view
98        }};
99    }
100
101    #[test]
102    fn timestamp_view_serialize_matches_owned() {
103        let json = assert_view_json_parity!(
104            TimestampView,
105            Timestamp {
106                seconds: 1_700_000_000,
107                nanos: 123_456_789,
108                ..Default::default()
109            }
110        );
111        // Sanity: the JSON string is actually RFC 3339, not a struct.
112        assert_eq!(json, r#""2023-11-14T22:13:20.123456789Z""#);
113    }
114
115    #[test]
116    fn duration_view_serialize_matches_owned() {
117        let json = assert_view_json_parity!(
118            DurationView,
119            Duration {
120                seconds: 1,
121                nanos: 500_000_000,
122                ..Default::default()
123            }
124        );
125        assert_eq!(json, r#""1.500s""#);
126    }
127
128    #[test]
129    fn field_mask_view_serialize_matches_owned() {
130        let json = assert_view_json_parity!(
131            FieldMaskView,
132            FieldMask {
133                paths: vec!["user.display_name".into(), "photo".into()],
134                ..Default::default()
135            }
136        );
137        assert_eq!(json, r#""user.displayName,photo""#);
138    }
139
140    #[test]
141    fn wrapper_views_serialize_match_owned() {
142        assert_view_json_parity!(
143            BoolValueView,
144            BoolValue {
145                value: true,
146                ..Default::default()
147            }
148        );
149        assert_view_json_parity!(
150            BytesValueView,
151            BytesValue {
152                value: vec![0xDE, 0xAD],
153                ..Default::default()
154            }
155        );
156        assert_view_json_parity!(
157            DoubleValueView,
158            DoubleValue {
159                value: f64::INFINITY,
160                ..Default::default()
161            }
162        );
163        assert_view_json_parity!(
164            FloatValueView,
165            FloatValue {
166                value: 1.5,
167                ..Default::default()
168            }
169        );
170        assert_view_json_parity!(
171            Int32ValueView,
172            Int32Value {
173                value: -5,
174                ..Default::default()
175            }
176        );
177        assert_view_json_parity!(
178            Int64ValueView,
179            Int64Value {
180                value: 9_007_199_254_740_993,
181                ..Default::default()
182            }
183        );
184        assert_view_json_parity!(
185            StringValueView,
186            StringValue {
187                value: "hi".into(),
188                ..Default::default()
189            }
190        );
191        assert_view_json_parity!(
192            UInt32ValueView,
193            UInt32Value {
194                value: u32::MAX,
195                ..Default::default()
196            }
197        );
198        assert_view_json_parity!(
199            UInt64ValueView,
200            UInt64Value {
201                value: u64::MAX,
202                ..Default::default()
203            }
204        );
205    }
206
207    #[test]
208    fn struct_value_listvalue_views_serialize_match_owned() {
209        assert_view_json_parity!(
210            ValueView,
211            Value {
212                kind: Some(Kind::StringValue("x".into())),
213                ..Default::default()
214            }
215        );
216        assert_view_json_parity!(
217            ListValueView,
218            ListValue {
219                values: vec![
220                    Value {
221                        kind: Some(Kind::NumberValue(1.0)),
222                        ..Default::default()
223                    },
224                    Value {
225                        kind: Some(Kind::BoolValue(true)),
226                        ..Default::default()
227                    },
228                ],
229                ..Default::default()
230            }
231        );
232        assert_view_json_parity!(
233            StructView,
234            Struct {
235                fields: [(
236                    "k".to_string(),
237                    Value {
238                        kind: Some(Kind::StringValue("v".into())),
239                        ..Default::default()
240                    },
241                )]
242                .into_iter()
243                .collect(),
244                ..Default::default()
245            }
246        );
247    }
248
249    #[test]
250    fn empty_view_serialize_matches_owned() {
251        let json = assert_view_json_parity!(EmptyView, Empty::default());
252        assert_eq!(json, "{}");
253    }
254}