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