common_error/
status_code.rs1use std::fmt;
16
17use strum::{AsRefStr, EnumIter, EnumString, FromRepr};
18use tonic::Code;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, AsRefStr, EnumIter, FromRepr)]
22pub enum StatusCode {
23 Success = 0,
26
27 Unknown = 1000,
29 Unsupported = 1001,
31 Unexpected = 1002,
33 Internal = 1003,
35 InvalidArguments = 1004,
37 Cancelled = 1005,
39 IllegalState = 1006,
41 External = 1007,
43 DeadlineExceeded = 1008,
45 InvalidSyntax = 2000,
50 PlanQuery = 3000,
55 EngineExecuteQuery = 3001,
57 TableAlreadyExists = 4000,
62 TableNotFound = 4001,
64 TableColumnNotFound = 4002,
66 TableColumnExists = 4003,
68 DatabaseNotFound = 4004,
70 RegionNotFound = 4005,
72 RegionAlreadyExists = 4006,
74 RegionReadonly = 4007,
76 RegionNotReady = 4008,
78 RegionBusy = 4009,
80 TableUnavailable = 4010,
82 DatabaseAlreadyExists = 4011,
84 StorageUnavailable = 5000,
89 RequestOutdated = 5001,
91 RuntimeResourcesExhausted = 6000,
96
97 RateLimited = 6001,
99 UserNotFound = 7000,
104 UnsupportedPasswordType = 7001,
106 UserPasswordMismatch = 7002,
108 AuthHeaderNotFound = 7003,
110 InvalidAuthHeader = 7004,
112 AccessDenied = 7005,
114 PermissionDenied = 7006,
116 FlowAlreadyExists = 8000,
120 FlowNotFound = 8001,
121 }
123
124impl StatusCode {
125 pub fn is_success(code: u32) -> bool {
127 Self::Success as u32 == code
128 }
129
130 pub fn is_retryable(&self) -> bool {
132 match self {
133 StatusCode::StorageUnavailable
134 | StatusCode::RuntimeResourcesExhausted
135 | StatusCode::Internal
136 | StatusCode::RegionNotReady
137 | StatusCode::TableUnavailable
138 | StatusCode::RegionBusy => true,
139
140 StatusCode::Success
141 | StatusCode::Unknown
142 | StatusCode::Unsupported
143 | StatusCode::IllegalState
144 | StatusCode::Unexpected
145 | StatusCode::InvalidArguments
146 | StatusCode::Cancelled
147 | StatusCode::DeadlineExceeded
148 | StatusCode::InvalidSyntax
149 | StatusCode::DatabaseAlreadyExists
150 | StatusCode::PlanQuery
151 | StatusCode::EngineExecuteQuery
152 | StatusCode::TableAlreadyExists
153 | StatusCode::TableNotFound
154 | StatusCode::RegionAlreadyExists
155 | StatusCode::RegionNotFound
156 | StatusCode::FlowAlreadyExists
157 | StatusCode::FlowNotFound
158 | StatusCode::RegionReadonly
159 | StatusCode::TableColumnNotFound
160 | StatusCode::TableColumnExists
161 | StatusCode::DatabaseNotFound
162 | StatusCode::RateLimited
163 | StatusCode::UserNotFound
164 | StatusCode::UnsupportedPasswordType
165 | StatusCode::UserPasswordMismatch
166 | StatusCode::AuthHeaderNotFound
167 | StatusCode::InvalidAuthHeader
168 | StatusCode::AccessDenied
169 | StatusCode::PermissionDenied
170 | StatusCode::RequestOutdated
171 | StatusCode::External => false,
172 }
173 }
174
175 pub fn should_log_error(&self) -> bool {
178 match self {
179 StatusCode::Unknown
180 | StatusCode::Unexpected
181 | StatusCode::Internal
182 | StatusCode::Cancelled
183 | StatusCode::DeadlineExceeded
184 | StatusCode::IllegalState
185 | StatusCode::EngineExecuteQuery
186 | StatusCode::StorageUnavailable
187 | StatusCode::RuntimeResourcesExhausted
188 | StatusCode::External => true,
189
190 StatusCode::Success
191 | StatusCode::Unsupported
192 | StatusCode::InvalidArguments
193 | StatusCode::InvalidSyntax
194 | StatusCode::TableAlreadyExists
195 | StatusCode::TableNotFound
196 | StatusCode::RegionAlreadyExists
197 | StatusCode::RegionNotFound
198 | StatusCode::PlanQuery
199 | StatusCode::FlowAlreadyExists
200 | StatusCode::FlowNotFound
201 | StatusCode::RegionNotReady
202 | StatusCode::RegionBusy
203 | StatusCode::RegionReadonly
204 | StatusCode::TableColumnNotFound
205 | StatusCode::TableColumnExists
206 | StatusCode::DatabaseNotFound
207 | StatusCode::RateLimited
208 | StatusCode::UserNotFound
209 | StatusCode::TableUnavailable
210 | StatusCode::DatabaseAlreadyExists
211 | StatusCode::UnsupportedPasswordType
212 | StatusCode::UserPasswordMismatch
213 | StatusCode::AuthHeaderNotFound
214 | StatusCode::InvalidAuthHeader
215 | StatusCode::AccessDenied
216 | StatusCode::PermissionDenied
217 | StatusCode::RequestOutdated => false,
218 }
219 }
220
221 pub fn from_u32(value: u32) -> Option<Self> {
222 StatusCode::from_repr(value as usize)
223 }
224}
225
226impl fmt::Display for StatusCode {
227 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228 write!(f, "{self:?}")
230 }
231}
232
233#[macro_export]
234macro_rules! define_into_tonic_status {
235 ($Error: ty) => {
236 impl From<$Error> for tonic::Status {
237 fn from(err: $Error) -> Self {
238 use tonic::codegen::http::{HeaderMap, HeaderValue};
239 use tonic::metadata::MetadataMap;
240 use $crate::GREPTIME_DB_HEADER_ERROR_CODE;
241
242 let mut headers = HeaderMap::<HeaderValue>::with_capacity(2);
243
244 let status_code = err.status_code();
247 headers.insert(
248 GREPTIME_DB_HEADER_ERROR_CODE,
249 HeaderValue::from(status_code as u32),
250 );
251 let root_error = err.output_msg();
252
253 let metadata = MetadataMap::from_headers(headers);
254 tonic::Status::with_metadata(
255 $crate::status_code::status_to_tonic_code(status_code),
256 root_error,
257 metadata,
258 )
259 }
260 }
261 };
262}
263
264pub fn status_to_tonic_code(status_code: StatusCode) -> Code {
266 match status_code {
267 StatusCode::Success => Code::Ok,
268 StatusCode::Unknown | StatusCode::External => Code::Unknown,
269 StatusCode::Unsupported => Code::Unimplemented,
270 StatusCode::Unexpected
271 | StatusCode::IllegalState
272 | StatusCode::Internal
273 | StatusCode::PlanQuery
274 | StatusCode::EngineExecuteQuery => Code::Internal,
275 StatusCode::InvalidArguments | StatusCode::InvalidSyntax | StatusCode::RequestOutdated => {
276 Code::InvalidArgument
277 }
278 StatusCode::Cancelled => Code::Cancelled,
279 StatusCode::DeadlineExceeded => Code::DeadlineExceeded,
280 StatusCode::TableAlreadyExists
281 | StatusCode::TableColumnExists
282 | StatusCode::RegionAlreadyExists
283 | StatusCode::DatabaseAlreadyExists
284 | StatusCode::FlowAlreadyExists => Code::AlreadyExists,
285 StatusCode::TableNotFound
286 | StatusCode::RegionNotFound
287 | StatusCode::TableColumnNotFound
288 | StatusCode::DatabaseNotFound
289 | StatusCode::UserNotFound
290 | StatusCode::FlowNotFound => Code::NotFound,
291 StatusCode::TableUnavailable
292 | StatusCode::StorageUnavailable
293 | StatusCode::RegionNotReady => Code::Unavailable,
294 StatusCode::RuntimeResourcesExhausted
295 | StatusCode::RateLimited
296 | StatusCode::RegionBusy => Code::ResourceExhausted,
297 StatusCode::UnsupportedPasswordType
298 | StatusCode::UserPasswordMismatch
299 | StatusCode::AuthHeaderNotFound
300 | StatusCode::InvalidAuthHeader => Code::Unauthenticated,
301 StatusCode::AccessDenied | StatusCode::PermissionDenied | StatusCode::RegionReadonly => {
302 Code::PermissionDenied
303 }
304 }
305}
306
307pub fn convert_tonic_code_to_status_code(code: Code) -> StatusCode {
309 match code {
310 Code::Cancelled => StatusCode::Cancelled,
311 Code::DeadlineExceeded => StatusCode::DeadlineExceeded,
312 _ => StatusCode::Internal,
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 use strum::IntoEnumIterator;
319
320 use super::*;
321
322 fn assert_status_code_display(code: StatusCode, msg: &str) {
323 let code_msg = format!("{code}");
324 assert_eq!(msg, code_msg);
325 }
326
327 #[test]
328 fn test_display_status_code() {
329 assert_status_code_display(StatusCode::Unknown, "Unknown");
330 assert_status_code_display(StatusCode::TableAlreadyExists, "TableAlreadyExists");
331 }
332
333 #[test]
334 fn test_from_u32() {
335 for code in StatusCode::iter() {
336 let num = code as u32;
337 assert_eq!(StatusCode::from_u32(num), Some(code));
338 }
339
340 assert_eq!(StatusCode::from_u32(10000), None);
341 }
342
343 #[test]
344 fn test_is_success() {
345 assert!(StatusCode::is_success(0));
346 assert!(!StatusCode::is_success(1));
347 assert!(!StatusCode::is_success(2));
348 assert!(!StatusCode::is_success(3));
349 }
350}