mz_catalog/durable/upgrade/
wire_compatible.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10use bytes::Bytes;
11
12/// Denotes that `Self` is wire compatible with type `T`.
13///
14/// You should not implement this yourself, instead use the `wire_compatible!` macro.
15pub unsafe trait WireCompatible<T: prost::Message>: prost::Message + Default {
16    /// Converts the type `T` into `Self` by serializing `T` and deserializing as `Self`.
17    fn convert(old: &T) -> Self {
18        let bytes = old.encode_to_vec();
19        // Note: use Bytes to enable possible re-use of the underlying buffer.
20        let bytes = Bytes::from(bytes);
21        Self::decode(bytes).expect("wire compatible")
22    }
23}
24
25// SAFETY: A message type is trivially wire compatible with itself.
26unsafe impl<T: prost::Message + Default + Clone> WireCompatible<T> for T {
27    fn convert(old: &Self) -> Self {
28        old.clone()
29    }
30}
31
32/// Defines one protobuf type as wire compatible with another.
33///
34/// ```text
35/// wire_compatible!(objects_v28::DatabaseKey with objects_v27::DatabaseKey);
36/// ```
37///
38/// Internally this will implement the `WireCompatible<B> for <A>`, e.g.
39/// `WireCompatible<objects_v27::DatabaseKey> for objects_v28::DatabaseKey` and generate `proptest`
40/// cases that will create arbitrary objects of type `B` and assert they can be deserialized with
41/// type `A`, and vice versa.
42#[macro_export]
43macro_rules! wire_compatible {
44    ($a:ident $(:: $a_sub:ident)* with $b:ident $(:: $b_sub:ident)*) => {
45        ::static_assertions::assert_impl_all!(
46            $a $(::$a_sub)* : ::proptest::arbitrary::Arbitrary, ::prost::Message, Default,
47        );
48        ::static_assertions::assert_impl_all!(
49            $b $(::$b_sub)*  : ::proptest::arbitrary::Arbitrary, ::prost::Message, Default,
50        );
51
52        // SAFETY: Below we assert that these types are wire compatible by generating arbitrary
53        // structs, encoding in one, and then decoding in the other.
54        unsafe impl $crate::durable::upgrade::wire_compatible::WireCompatible< $b $(::$b_sub)* > for $a $(::$a_sub)* {}
55        unsafe impl $crate::durable::upgrade::wire_compatible::WireCompatible< $a $(::$a_sub)* > for $b $(::$b_sub)* {}
56
57        ::paste::paste! {
58            ::proptest::proptest! {
59                #[mz_ore::test]
60                #[cfg_attr(miri, ignore)] // slow
61                fn [<proptest_wire_compat_ $a:snake $(_$a_sub:snake)* _to_ $b:snake $(_$b_sub:snake)* >](a: $a $(::$a_sub)* ) {
62                    use ::prost::Message;
63                    let a_bytes = a.encode_to_vec();
64                    let b_decoded = $b $(::$b_sub)*::decode(&a_bytes[..]);
65                    ::proptest::prelude::prop_assert!(b_decoded.is_ok());
66
67                    // Maybe superfluous, but this is a method called in production.
68                    let b_decoded = b_decoded.expect("asserted Ok");
69                    let b_converted: $b $(::$b_sub)* = $crate::durable::upgrade::wire_compatible::WireCompatible::convert(&a);
70                    assert_eq!(b_decoded, b_converted);
71
72                    let b_bytes = b_decoded.encode_to_vec();
73                    ::proptest::prelude::prop_assert_eq!(a_bytes, b_bytes, "a and b serialize differently");
74                }
75
76                #[mz_ore::test]
77                #[cfg_attr(miri, ignore)] // slow
78                fn [<proptest_wire_compat_ $b:snake $(_$b_sub:snake)* _to_ $a:snake $(_$a_sub:snake)* >](b: $b $(::$b_sub)* ) {
79                    use ::prost::Message;
80                    let b_bytes = b.encode_to_vec();
81                    let a_decoded = $a $(::$a_sub)*::decode(&b_bytes[..]);
82                    ::proptest::prelude::prop_assert!(a_decoded.is_ok());
83
84                    // Maybe superfluous, but this is a method called in production.
85                    let a_decoded = a_decoded.expect("asserted Ok");
86                    let a_converted: $a $(::$a_sub)* = $crate::durable::upgrade::wire_compatible::WireCompatible::convert(&b);
87                    assert_eq!(a_decoded, a_converted);
88
89                    let a_bytes = a_decoded.encode_to_vec();
90                    ::proptest::prelude::prop_assert_eq!(a_bytes, b_bytes, "a and b serialize differently");
91                }
92            }
93        }
94    };
95}
96pub use wire_compatible;