Skip to main content

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)* :
49                ::proptest::arbitrary::Arbitrary,
50                ::serde::Serialize,
51                ::serde::de::DeserializeOwned,
52        );
53        ::static_assertions::assert_impl_all!(
54            $b $(::$b_sub)* :
55                ::proptest::arbitrary::Arbitrary,
56                ::serde::Serialize,
57                ::serde::de::DeserializeOwned,
58        );
59
60        // SAFETY: Below we assert that these types are JSON
61        // compatible by generating arbitrary structs, encoding in
62        // one, and then decoding in the other.
63        unsafe impl $crate::durable::upgrade::json_compatible
64            ::JsonCompatible< $b $(::$b_sub)* >
65            for $a $(::$a_sub)* {}
66        unsafe impl $crate::durable::upgrade::json_compatible
67            ::JsonCompatible< $a $(::$a_sub)* >
68            for $b $(::$b_sub)* {}
69
70        ::paste::paste! {
71            ::proptest::proptest! {
72                #![proptest_config(::proptest::test_runner::Config {
73                    cases: 64,
74                    ..Default::default()
75                })]
76
77                #[mz_ore::test]
78                #[cfg_attr(miri, ignore)] // slow
79                fn [<proptest_json_compat_
80                    $a:snake $(_$a_sub:snake)* _to_
81                    $b:snake $(_$b_sub:snake)*
82                >](a: $a $(::$a_sub)* ) {
83                    let a_bytes = ::serde_json::to_vec(&a)
84                        .expect("JSON serializable");
85                    let b_decoded = ::serde_json::from_slice
86                        ::<$b $(::$b_sub)*>(&a_bytes);
87                    ::proptest::prelude::prop_assert!(
88                        b_decoded.is_ok(),
89                    );
90
91                    // Maybe superfluous, but this is a method
92                    // called in production.
93                    let b_decoded = b_decoded.expect("asserted Ok");
94                    use $crate::durable::upgrade::json_compatible
95                        ::JsonCompatible;
96                    let b_converted: $b $(::$b_sub)* =
97                        JsonCompatible::convert(&a);
98                    assert_eq!(b_decoded, b_converted);
99
100                    let b_bytes = ::serde_json::to_vec(&b_decoded)
101                        .expect("JSON serializable");
102                    ::proptest::prelude::prop_assert_eq!(
103                        a_bytes, b_bytes,
104                        "a and b serialize differently",
105                    );
106                }
107
108                #[mz_ore::test]
109                #[cfg_attr(miri, ignore)] // slow
110                fn [<proptest_json_compat_
111                    $b:snake $(_$b_sub:snake)* _to_
112                    $a:snake $(_$a_sub:snake)*
113                >](b: $b $(::$b_sub)* ) {
114                    let b_bytes = ::serde_json::to_vec(&b)
115                        .expect("JSON serializable");
116                    let a_decoded = ::serde_json::from_slice
117                        ::<$a $(::$a_sub)*>(&b_bytes);
118                    ::proptest::prelude::prop_assert!(
119                        a_decoded.is_ok(),
120                    );
121
122                    // Maybe superfluous, but this is a method
123                    // called in production.
124                    let a_decoded = a_decoded.expect("asserted Ok");
125                    use $crate::durable::upgrade::json_compatible
126                        ::JsonCompatible;
127                    let a_converted: $a $(::$a_sub)* =
128                        JsonCompatible::convert(&b);
129                    assert_eq!(a_decoded, a_converted);
130
131                    let a_bytes = ::serde_json::to_vec(&a_decoded)
132                        .expect("JSON serializable");
133                    ::proptest::prelude::prop_assert_eq!(
134                        a_bytes, b_bytes,
135                        "a and b serialize differently",
136                    );
137                }
138            }
139        }
140    };
141}
142pub use json_compatible;