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().serialize(s)
48                }
49            }
50        )+
51    };
52}
53
54wkt_view_serialize!(
55    AnyView,
56    BoolValueView,
57    BytesValueView,
58    DoubleValueView,
59    DurationView,
60    EmptyView,
61    FieldMaskView,
62    FloatValueView,
63    Int32ValueView,
64    Int64ValueView,
65    ListValueView,
66    StringValueView,
67    StructView,
68    TimestampView,
69    UInt32ValueView,
70    UInt64ValueView,
71    ValueView,
72);
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77    use crate::google::protobuf::{
78        value::Kind, BoolValue, BytesValue, DoubleValue, Duration, Empty, FieldMask, FloatValue,
79        Int32Value, Int64Value, ListValue, StringValue, Struct, Timestamp, UInt32Value,
80        UInt64Value, Value,
81    };
82    use buffa::Message;
83
84    /// Encode `$owned`, decode it as `$view`, serialize both to JSON, and
85    /// assert the outputs match.  Returns the view's JSON string for
86    /// follow-on assertions.
87    macro_rules! assert_view_json_parity {
88        ($view:ty, $owned:expr) => {{
89            let owned = $owned;
90            let bytes = owned.encode_to_vec();
91            let view = <$view>::decode_view(&bytes).expect("decode_view");
92            let json_owned = serde_json::to_string(&owned).expect("serialize owned");
93            let json_view = serde_json::to_string(&view).expect("serialize view");
94            assert_eq!(json_view, json_owned, "view JSON must match owned JSON");
95            json_view
96        }};
97    }
98
99    #[test]
100    fn timestamp_view_serialize_matches_owned() {
101        let json = assert_view_json_parity!(
102            TimestampView,
103            Timestamp {
104                seconds: 1_700_000_000,
105                nanos: 123_456_789,
106                ..Default::default()
107            }
108        );
109        // Sanity: the JSON string is actually RFC 3339, not a struct.
110        assert_eq!(json, r#""2023-11-14T22:13:20.123456789Z""#);
111    }
112
113    #[test]
114    fn duration_view_serialize_matches_owned() {
115        let json = assert_view_json_parity!(
116            DurationView,
117            Duration {
118                seconds: 1,
119                nanos: 500_000_000,
120                ..Default::default()
121            }
122        );
123        assert_eq!(json, r#""1.500s""#);
124    }
125
126    #[test]
127    fn field_mask_view_serialize_matches_owned() {
128        let json = assert_view_json_parity!(
129            FieldMaskView,
130            FieldMask {
131                paths: vec!["user.display_name".into(), "photo".into()],
132                ..Default::default()
133            }
134        );
135        assert_eq!(json, r#""user.displayName,photo""#);
136    }
137
138    #[test]
139    fn wrapper_views_serialize_match_owned() {
140        assert_view_json_parity!(
141            BoolValueView,
142            BoolValue {
143                value: true,
144                ..Default::default()
145            }
146        );
147        assert_view_json_parity!(
148            BytesValueView,
149            BytesValue {
150                value: vec![0xDE, 0xAD],
151                ..Default::default()
152            }
153        );
154        assert_view_json_parity!(
155            DoubleValueView,
156            DoubleValue {
157                value: f64::INFINITY,
158                ..Default::default()
159            }
160        );
161        assert_view_json_parity!(
162            FloatValueView,
163            FloatValue {
164                value: 1.5,
165                ..Default::default()
166            }
167        );
168        assert_view_json_parity!(
169            Int32ValueView,
170            Int32Value {
171                value: -5,
172                ..Default::default()
173            }
174        );
175        assert_view_json_parity!(
176            Int64ValueView,
177            Int64Value {
178                value: 9_007_199_254_740_993,
179                ..Default::default()
180            }
181        );
182        assert_view_json_parity!(
183            StringValueView,
184            StringValue {
185                value: "hi".into(),
186                ..Default::default()
187            }
188        );
189        assert_view_json_parity!(
190            UInt32ValueView,
191            UInt32Value {
192                value: u32::MAX,
193                ..Default::default()
194            }
195        );
196        assert_view_json_parity!(
197            UInt64ValueView,
198            UInt64Value {
199                value: u64::MAX,
200                ..Default::default()
201            }
202        );
203    }
204
205    #[test]
206    fn struct_value_listvalue_views_serialize_match_owned() {
207        assert_view_json_parity!(
208            ValueView,
209            Value {
210                kind: Some(Kind::StringValue("x".into())),
211                ..Default::default()
212            }
213        );
214        assert_view_json_parity!(
215            ListValueView,
216            ListValue {
217                values: vec![
218                    Value {
219                        kind: Some(Kind::NumberValue(1.0)),
220                        ..Default::default()
221                    },
222                    Value {
223                        kind: Some(Kind::BoolValue(true)),
224                        ..Default::default()
225                    },
226                ],
227                ..Default::default()
228            }
229        );
230        assert_view_json_parity!(
231            StructView,
232            Struct {
233                fields: [(
234                    "k".to_string(),
235                    Value {
236                        kind: Some(Kind::StringValue("v".into())),
237                        ..Default::default()
238                    },
239                )]
240                .into_iter()
241                .collect(),
242                ..Default::default()
243            }
244        );
245    }
246
247    #[test]
248    fn empty_view_serialize_matches_owned() {
249        let json = assert_view_json_parity!(EmptyView, Empty::default());
250        assert_eq!(json, "{}");
251    }
252}