mz_catalog/durable/upgrade/
json_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 serde::Serialize;
11use serde::de::DeserializeOwned;
12
13/// Denotes that `Self` is JSON compatible with type `T`.
14///
15/// You should not implement this yourself, instead use the `json_compatible!` macro.
16pub unsafe trait JsonCompatible<T>: Serialize + DeserializeOwned
17where
18    T: Serialize + DeserializeOwned,
19{
20    /// Converts the type `T` into `Self` by serializing `T` and deserializing as `Self`.
21    fn convert(old: &T) -> Self {
22        let bytes = serde_json::to_vec(old).expect("JSON serializable");
23        serde_json::from_slice(&bytes).expect("JSON compatible")
24    }
25}
26
27// SAFETY: A type is trivially JSON compatible with itself.
28unsafe impl<T: Serialize + DeserializeOwned + Clone> JsonCompatible<T> for T {
29    fn convert(old: &Self) -> Self {
30        old.clone()
31    }
32}
33
34/// Defines one type as JSON compatible with another.
35///
36/// ```text
37/// json_compatible!(objects_v28::DatabaseKey with objects_v27::DatabaseKey);
38/// ```
39///
40/// Internally this will implement `JsonCompatible<B> for <A>`, e.g.
41/// `JsonCompatible<objects_v27::DatabaseKey> for objects_v28::DatabaseKey` and generate `proptest`
42/// cases that will create arbitrary objects of type `B` and assert they can be deserialized with
43/// type `A`, and vice versa.
44#[macro_export]
45macro_rules! json_compatible {
46    ($a:ident $(:: $a_sub:ident)* with $b:ident $(:: $b_sub:ident)*) => {
47        ::static_assertions::assert_impl_all!(
48            $a $(::$a_sub)* : ::proptest::arbitrary::Arbitrary, ::serde::Serialize, ::serde::de::DeserializeOwned,
49        );
50        ::static_assertions::assert_impl_all!(
51            $b $(::$b_sub)*  : ::proptest::arbitrary::Arbitrary, ::serde::Serialize, ::serde::de::DeserializeOwned,
52        );
53
54        // SAFETY: Below we assert that these types are JSON compatible by generating arbitrary
55        // structs, encoding in one, and then decoding in the other.
56        unsafe impl $crate::durable::upgrade::json_compatible::JsonCompatible< $b $(::$b_sub)* > for $a $(::$a_sub)* {}
57        unsafe impl $crate::durable::upgrade::json_compatible::JsonCompatible< $a $(::$a_sub)* > for $b $(::$b_sub)* {}
58
59        ::paste::paste! {
60            ::proptest::proptest! {
61                #![proptest_config(::proptest::test_runner::Config {
62                    cases: 64,
63                    ..Default::default()
64                })]
65
66                #[mz_ore::test]
67                #[cfg_attr(miri, ignore)] // slow
68                fn [<proptest_json_compat_ $a:snake $(_$a_sub:snake)* _to_ $b:snake $(_$b_sub:snake)* >](a: $a $(::$a_sub)* ) {
69                    let a_bytes = ::serde_json::to_vec(&a).expect("JSON serializable");
70                    let b_decoded = ::serde_json::from_slice::<$b $(::$b_sub)*>(&a_bytes);
71                    ::proptest::prelude::prop_assert!(b_decoded.is_ok());
72
73                    // Maybe superfluous, but this is a method called in production.
74                    let b_decoded = b_decoded.expect("asserted Ok");
75                    let b_converted: $b $(::$b_sub)* = $crate::durable::upgrade::json_compatible::JsonCompatible::convert(&a);
76                    assert_eq!(b_decoded, b_converted);
77
78                    let b_bytes = ::serde_json::to_vec(&b_decoded).expect("JSON serializable");
79                    ::proptest::prelude::prop_assert_eq!(a_bytes, b_bytes, "a and b serialize differently");
80                }
81
82                #[mz_ore::test]
83                #[cfg_attr(miri, ignore)] // slow
84                fn [<proptest_json_compat_ $b:snake $(_$b_sub:snake)* _to_ $a:snake $(_$a_sub:snake)* >](b: $b $(::$b_sub)* ) {
85                    let b_bytes = ::serde_json::to_vec(&b).expect("JSON serializable");
86                    let a_decoded = ::serde_json::from_slice::<$a $(::$a_sub)*>(&b_bytes);
87                    ::proptest::prelude::prop_assert!(a_decoded.is_ok());
88
89                    // Maybe superfluous, but this is a method called in production.
90                    let a_decoded = a_decoded.expect("asserted Ok");
91                    let a_converted: $a $(::$a_sub)* = $crate::durable::upgrade::json_compatible::JsonCompatible::convert(&b);
92                    assert_eq!(a_decoded, a_converted);
93
94                    let a_bytes = ::serde_json::to_vec(&a_decoded).expect("JSON serializable");
95                    ::proptest::prelude::prop_assert_eq!(a_bytes, b_bytes, "a and b serialize differently");
96                }
97            }
98        }
99    };
100}
101pub use json_compatible;