risingwave_error/
macros.rs

1// Copyright 2025 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15/// Util macro for generating error when condition check failed.
16///
17/// # Case 1: Expression only.
18/// ```ignore
19/// ensure!(a < 0);
20/// ```
21/// This will generate following error:
22/// ```ignore
23/// anyhow!("a < 0").into()
24/// ```
25///
26/// # Case 2: Error message only.
27/// ```ignore
28/// ensure!(a < 0, "a should not be negative!");
29/// ```
30/// This will generate following error:
31/// ```ignore
32/// anyhow!("a should not be negative!").into();
33/// ```
34///
35/// # Case 3: Error message with argument.
36/// ```ignore
37/// ensure!(a < 0, "a should not be negative, value: {}", 1);
38/// ```
39/// This will generate following error:
40/// ```ignore
41/// anyhow!("a should not be negative, value: 1").into();
42/// ```
43///
44/// # Case 4: Error code.
45/// ```ignore
46/// ensure!(a < 0, ErrorCode::MemoryError { layout });
47/// ```
48/// This will generate following error:
49/// ```ignore
50/// ErrorCode::MemoryError { layout }.into();
51/// ```
52#[macro_export]
53macro_rules! ensure {
54    ($cond:expr $(,)?) => {
55        if !$cond {
56            return Err(::anyhow::anyhow!(stringify!($cond)).into());
57        }
58    };
59    ($cond:expr, $msg:literal $(,)?) => {
60        if !$cond {
61            return Err(::anyhow::anyhow!($msg).into());
62        }
63    };
64    ($cond:expr, $fmt:expr, $($arg:tt)*) => {
65        if !$cond {
66            return Err(::anyhow::anyhow!($fmt, $($arg)*).into());
67        }
68    };
69    ($cond:expr, $error_code:expr) => {
70        if !$cond {
71            return Err($error_code.into());
72        }
73    };
74}
75pub use ensure;
76
77/// Util macro to generate error when the two arguments are not equal.
78#[macro_export]
79macro_rules! ensure_eq {
80    ($left:expr, $right:expr) => {
81        match (&$left, &$right) {
82            (left_val, right_val) => {
83                if !(left_val == right_val) {
84                    $crate::bail!(
85                        "{} == {} assertion failed ({} is {}, {} is {})",
86                        stringify!($left),
87                        stringify!($right),
88                        stringify!($left),
89                        &*left_val,
90                        stringify!($right),
91                        &*right_val,
92                    );
93                }
94            }
95        }
96    };
97}
98pub use ensure_eq;
99
100/// Return early with an error, in any type that can be converted from
101/// an [`anyhow::Error`].
102///
103/// See [`anyhow::bail`] for more details.
104#[macro_export]
105macro_rules! bail {
106    ($($arg:tt)*) => {
107        return Err(::anyhow::anyhow!($($arg)*).into())
108    };
109}
110pub use bail;
111
112/// Try to match an enum variant and return the internal value.
113///
114/// Return an [`anyhow::Error`] if the enum variant does not match.
115#[macro_export]
116macro_rules! try_match_expand {
117    ($e:expr, $variant:path) => {
118        match $e {
119            $variant(internal) => Ok(internal),
120            _ => Err(::anyhow::anyhow!(
121                "unable to match {} with {}",
122                stringify!($e),
123                stringify!($variant),
124            )),
125        }
126    };
127    ($e:expr, $variant:path, $($arg:tt)+) => {
128        match $e {
129            $variant(internal) => Ok(internal),
130            _ => Err(::anyhow::anyhow!($($arg)+)),
131        }
132    };
133}
134pub use try_match_expand;
135
136/// Match an enum variant and return the internal value.
137///
138/// Panic if the enum variant does not match.
139#[macro_export]
140macro_rules! must_match {
141    ($expression:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )? => $action:expr) => {
142        match $expression {
143            $( $pattern )|+ $( if $guard )? => $action,
144            _ => panic!("enum variant mismatch: `{}` is required", stringify!($( $pattern )|+ $( if $guard )?)),
145        }
146    };
147}
148pub use must_match;
149
150#[cfg(test)]
151mod ensure_tests {
152    use anyhow::anyhow;
153    use thiserror::Error;
154
155    #[derive(Error, Debug)]
156    #[error(transparent)]
157    struct MyError(#[from] anyhow::Error);
158
159    #[test]
160    fn test_ensure() {
161        let a = 1;
162
163        {
164            let err_msg = "a < 0";
165            let error = (|| {
166                ensure!(a < 0);
167                Ok::<_, MyError>(())
168            })()
169            .unwrap_err();
170
171            assert_eq!(MyError(anyhow!(err_msg)).to_string(), error.to_string(),);
172        }
173
174        {
175            let err_msg = "error msg without args";
176            let error = (|| {
177                ensure!(a < 0, "error msg without args");
178                Ok::<_, MyError>(())
179            })()
180            .unwrap_err();
181            assert_eq!(MyError(anyhow!(err_msg)).to_string(), error.to_string());
182        }
183
184        {
185            let error = (|| {
186                ensure!(a < 0, "error msg with args: {}", "xx");
187                Ok::<_, MyError>(())
188            })()
189            .unwrap_err();
190            assert_eq!(
191                MyError(anyhow!("error msg with args: {}", "xx")).to_string(),
192                error.to_string()
193            );
194        }
195    }
196
197    #[test]
198    fn test_ensure_eq() {
199        fn ensure_a_equals_b() -> Result<(), MyError> {
200            let a = 1;
201            let b = 2;
202            ensure_eq!(a, b);
203            Ok(())
204        }
205        let err = ensure_a_equals_b().unwrap_err();
206        assert_eq!(err.to_string(), "a == b assertion failed (a is 1, b is 2)");
207    }
208}
209
210#[cfg(test)]
211mod match_tests {
212    #[derive(thiserror::Error, Debug)]
213    #[error(transparent)]
214    struct ExpandError(#[from] anyhow::Error);
215
216    #[allow(dead_code)]
217    enum MyEnum {
218        A(String),
219        B,
220    }
221
222    #[test]
223    fn test_try_match() -> Result<(), ExpandError> {
224        assert_eq!(
225            try_match_expand!(MyEnum::A("failure".to_owned()), MyEnum::A)?,
226            "failure"
227        );
228        assert_eq!(
229            try_match_expand!(MyEnum::A("failure".to_owned()), MyEnum::A)?,
230            "failure"
231        );
232        assert_eq!(
233            try_match_expand!(MyEnum::A("failure".to_owned()), MyEnum::A)?,
234            "failure"
235        );
236
237        // Test let statement is compilable.
238        let err_str = try_match_expand!(MyEnum::A("failure".to_owned()), MyEnum::A)?;
239        assert_eq!(err_str, "failure");
240        Ok(())
241    }
242
243    #[test]
244    fn test_must_match() -> Result<(), ExpandError> {
245        #[allow(dead_code)]
246        enum A {
247            Foo,
248            Bar,
249        }
250        let a = A::Foo;
251        let val = must_match!(a, A::Foo => 42);
252        assert_eq!(val, 42);
253
254        #[allow(dead_code)]
255        enum B {
256            Foo,
257            Bar(i32),
258            Baz { x: u32, y: u32 },
259        }
260        let b = B::Baz { x: 1, y: 2 };
261        let val = must_match!(b, B::Baz { x, y } if x == 1 => x + y);
262        assert_eq!(val, 3);
263        let b = B::Bar(42);
264        let val = must_match!(b, B::Bar(x) => {
265            let y = x + 1;
266            y * 2
267        });
268        assert_eq!(val, 86);
269
270        Ok(())
271    }
272}