1use 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 Suspended = 1009,
47 InvalidSyntax = 2000,
52 PlanQuery = 3000,
57 EngineExecuteQuery = 3001,
59 TableAlreadyExists = 4000,
64 TableNotFound = 4001,
66 TableColumnNotFound = 4002,
68 TableColumnExists = 4003,
70 DatabaseNotFound = 4004,
72 RegionNotFound = 4005,
74 RegionAlreadyExists = 4006,
76 RegionReadonly = 4007,
78 RegionNotReady = 4008,
80 RegionBusy = 4009,
82 TableUnavailable = 4010,
84 DatabaseAlreadyExists = 4011,
86 StorageUnavailable = 5000,
91 RequestOutdated = 5001,
93 RuntimeResourcesExhausted = 6000,
98
99 RateLimited = 6001,
101 UserNotFound = 7000,
106 UnsupportedPasswordType = 7001,
108 UserPasswordMismatch = 7002,
110 AuthHeaderNotFound = 7003,
112 InvalidAuthHeader = 7004,
114 AccessDenied = 7005,
116 PermissionDenied = 7006,
118 FlowAlreadyExists = 8000,
122 FlowNotFound = 8001,
123 TriggerAlreadyExists = 9000,
127 TriggerNotFound = 9001,
128 }
130
131impl StatusCode {
132 pub fn is_success(code: u32) -> bool {
134 Self::Success as u32 == code
135 }
136
137 pub fn is_retryable(&self) -> bool {
139 match self {
140 StatusCode::StorageUnavailable
141 | StatusCode::RuntimeResourcesExhausted
142 | StatusCode::Internal
143 | StatusCode::RegionNotReady
144 | StatusCode::TableUnavailable
145 | StatusCode::RegionBusy => true,
146
147 StatusCode::Success
148 | StatusCode::Unknown
149 | StatusCode::Unsupported
150 | StatusCode::IllegalState
151 | StatusCode::Unexpected
152 | StatusCode::InvalidArguments
153 | StatusCode::Cancelled
154 | StatusCode::DeadlineExceeded
155 | StatusCode::InvalidSyntax
156 | StatusCode::DatabaseAlreadyExists
157 | StatusCode::PlanQuery
158 | StatusCode::EngineExecuteQuery
159 | StatusCode::TableAlreadyExists
160 | StatusCode::TableNotFound
161 | StatusCode::RegionAlreadyExists
162 | StatusCode::RegionNotFound
163 | StatusCode::FlowAlreadyExists
164 | StatusCode::FlowNotFound
165 | StatusCode::TriggerAlreadyExists
166 | StatusCode::TriggerNotFound
167 | StatusCode::RegionReadonly
168 | StatusCode::TableColumnNotFound
169 | StatusCode::TableColumnExists
170 | StatusCode::DatabaseNotFound
171 | StatusCode::RateLimited
172 | StatusCode::UserNotFound
173 | StatusCode::UnsupportedPasswordType
174 | StatusCode::UserPasswordMismatch
175 | StatusCode::AuthHeaderNotFound
176 | StatusCode::InvalidAuthHeader
177 | StatusCode::AccessDenied
178 | StatusCode::PermissionDenied
179 | StatusCode::RequestOutdated
180 | StatusCode::External
181 | StatusCode::Suspended => false,
182 }
183 }
184
185 pub fn should_log_error(&self) -> bool {
188 match self {
189 StatusCode::Unknown
190 | StatusCode::Unexpected
191 | StatusCode::Internal
192 | StatusCode::Cancelled
193 | StatusCode::DeadlineExceeded
194 | StatusCode::IllegalState
195 | StatusCode::EngineExecuteQuery
196 | StatusCode::StorageUnavailable
197 | StatusCode::RuntimeResourcesExhausted
198 | StatusCode::External => true,
199
200 StatusCode::Success
201 | StatusCode::Unsupported
202 | StatusCode::InvalidArguments
203 | StatusCode::InvalidSyntax
204 | StatusCode::TableAlreadyExists
205 | StatusCode::TableNotFound
206 | StatusCode::RegionAlreadyExists
207 | StatusCode::RegionNotFound
208 | StatusCode::PlanQuery
209 | StatusCode::FlowAlreadyExists
210 | StatusCode::FlowNotFound
211 | StatusCode::TriggerAlreadyExists
212 | StatusCode::TriggerNotFound
213 | StatusCode::RegionNotReady
214 | StatusCode::RegionBusy
215 | StatusCode::RegionReadonly
216 | StatusCode::TableColumnNotFound
217 | StatusCode::TableColumnExists
218 | StatusCode::DatabaseNotFound
219 | StatusCode::RateLimited
220 | StatusCode::UserNotFound
221 | StatusCode::TableUnavailable
222 | StatusCode::DatabaseAlreadyExists
223 | StatusCode::UnsupportedPasswordType
224 | StatusCode::UserPasswordMismatch
225 | StatusCode::AuthHeaderNotFound
226 | StatusCode::InvalidAuthHeader
227 | StatusCode::AccessDenied
228 | StatusCode::PermissionDenied
229 | StatusCode::RequestOutdated
230 | StatusCode::Suspended => false,
231 }
232 }
233
234 pub fn from_u32(value: u32) -> Option<Self> {
235 StatusCode::from_repr(value as usize)
236 }
237}
238
239impl fmt::Display for StatusCode {
240 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241 write!(f, "{self:?}")
243 }
244}
245
246#[macro_export]
247macro_rules! define_from_tonic_status {
248 ($Error: ty, $Variant: ident) => {
249 impl From<tonic::Status> for $Error {
250 fn from(e: tonic::Status) -> Self {
251 use snafu::location;
252
253 fn metadata_value(e: &tonic::Status, key: &str) -> Option<String> {
254 e.metadata()
255 .get(key)
256 .and_then(|v| String::from_utf8(v.as_bytes().to_vec()).ok())
257 }
258 let code = metadata_value(&e, $crate::GREPTIME_DB_HEADER_ERROR_CODE)
259 .and_then(|s| {
260 if let Ok(code) = s.parse::<u32>() {
261 StatusCode::from_u32(code)
262 } else {
263 None
264 }
265 })
266 .unwrap_or_else(|| match e.code() {
267 tonic::Code::Cancelled => StatusCode::Cancelled,
268 tonic::Code::DeadlineExceeded => StatusCode::DeadlineExceeded,
269 _ => StatusCode::Internal,
270 });
271
272 let msg = metadata_value(&e, $crate::GREPTIME_DB_HEADER_ERROR_MSG)
273 .unwrap_or_else(|| e.message().to_string());
274
275 Self::$Variant {
277 code,
278 msg,
279 tonic_code: e.code(),
280 location: location!(),
281 }
282 }
283 }
284 };
285}
286
287#[macro_export]
288macro_rules! define_into_tonic_status {
289 ($Error: ty) => {
290 impl From<$Error> for tonic::Status {
291 fn from(err: $Error) -> Self {
292 use tonic::codegen::http::{HeaderMap, HeaderValue};
293 use tonic::metadata::MetadataMap;
294 use $crate::GREPTIME_DB_HEADER_ERROR_CODE;
295
296 common_telemetry::error!(err; "Failed to handle request");
297
298 let mut headers = HeaderMap::<HeaderValue>::with_capacity(2);
299
300 let status_code = err.status_code();
303 headers.insert(
304 GREPTIME_DB_HEADER_ERROR_CODE,
305 HeaderValue::from(status_code as u32),
306 );
307 let root_error = err.output_msg();
308
309 let metadata = MetadataMap::from_headers(headers);
310 tonic::Status::with_metadata(
311 $crate::status_code::status_to_tonic_code(status_code),
312 root_error,
313 metadata,
314 )
315 }
316 }
317 };
318}
319
320pub fn status_to_tonic_code(status_code: StatusCode) -> Code {
322 match status_code {
323 StatusCode::Success => Code::Ok,
324 StatusCode::Unknown | StatusCode::External => Code::Unknown,
325 StatusCode::Unsupported => Code::Unimplemented,
326 StatusCode::Unexpected
327 | StatusCode::IllegalState
328 | StatusCode::Internal
329 | StatusCode::PlanQuery
330 | StatusCode::EngineExecuteQuery => Code::Internal,
331 StatusCode::InvalidArguments | StatusCode::InvalidSyntax | StatusCode::RequestOutdated => {
332 Code::InvalidArgument
333 }
334 StatusCode::Cancelled => Code::Cancelled,
335 StatusCode::DeadlineExceeded => Code::DeadlineExceeded,
336 StatusCode::TableAlreadyExists
337 | StatusCode::TableColumnExists
338 | StatusCode::RegionAlreadyExists
339 | StatusCode::DatabaseAlreadyExists
340 | StatusCode::TriggerAlreadyExists
341 | StatusCode::FlowAlreadyExists => Code::AlreadyExists,
342 StatusCode::TableNotFound
343 | StatusCode::RegionNotFound
344 | StatusCode::TableColumnNotFound
345 | StatusCode::DatabaseNotFound
346 | StatusCode::UserNotFound
347 | StatusCode::TriggerNotFound
348 | StatusCode::FlowNotFound => Code::NotFound,
349 StatusCode::TableUnavailable
350 | StatusCode::StorageUnavailable
351 | StatusCode::RegionNotReady => Code::Unavailable,
352 StatusCode::RuntimeResourcesExhausted
353 | StatusCode::RateLimited
354 | StatusCode::RegionBusy
355 | StatusCode::Suspended => Code::ResourceExhausted,
356 StatusCode::UnsupportedPasswordType
357 | StatusCode::UserPasswordMismatch
358 | StatusCode::AuthHeaderNotFound
359 | StatusCode::InvalidAuthHeader => Code::Unauthenticated,
360 StatusCode::AccessDenied | StatusCode::PermissionDenied | StatusCode::RegionReadonly => {
361 Code::PermissionDenied
362 }
363 }
364}
365
366#[cfg(test)]
367mod tests {
368 use strum::IntoEnumIterator;
369
370 use super::*;
371
372 fn assert_status_code_display(code: StatusCode, msg: &str) {
373 let code_msg = format!("{code}");
374 assert_eq!(msg, code_msg);
375 }
376
377 #[test]
378 fn test_display_status_code() {
379 assert_status_code_display(StatusCode::Unknown, "Unknown");
380 assert_status_code_display(StatusCode::TableAlreadyExists, "TableAlreadyExists");
381 }
382
383 #[test]
384 fn test_from_u32() {
385 for code in StatusCode::iter() {
386 let num = code as u32;
387 assert_eq!(StatusCode::from_u32(num), Some(code));
388 }
389
390 assert_eq!(StatusCode::from_u32(10000), None);
391 }
392
393 #[test]
394 fn test_is_success() {
395 assert!(StatusCode::is_success(0));
396 assert!(!StatusCode::is_success(1));
397 assert!(!StatusCode::is_success(2));
398 assert!(!StatusCode::is_success(3));
399 }
400}