1use std::fmt;
16
17use serde::{Deserialize, Deserializer, Serializer};
18use strum::{AsRefStr, EnumIter, EnumString, FromRepr};
19use tonic::Code;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, AsRefStr, EnumIter, FromRepr)]
23pub enum StatusCode {
24 Success = 0,
27
28 Unknown = 1000,
30 Unsupported = 1001,
32 Unexpected = 1002,
34 Internal = 1003,
36 InvalidArguments = 1004,
38 Cancelled = 1005,
40 IllegalState = 1006,
42 External = 1007,
44 DeadlineExceeded = 1008,
46 Suspended = 1009,
48 PartialFailure = 1010,
50 InvalidSyntax = 2000,
55 PlanQuery = 3000,
60 EngineExecuteQuery = 3001,
62 TableAlreadyExists = 4000,
67 TableNotFound = 4001,
69 TableColumnNotFound = 4002,
71 TableColumnExists = 4003,
73 DatabaseNotFound = 4004,
75 RegionNotFound = 4005,
77 RegionAlreadyExists = 4006,
79 RegionReadonly = 4007,
81 RegionNotReady = 4008,
83 RegionBusy = 4009,
85 TableUnavailable = 4010,
87 DatabaseAlreadyExists = 4011,
89 StorageUnavailable = 5000,
94 RequestOutdated = 5001,
96 RuntimeResourcesExhausted = 6000,
101
102 RateLimited = 6001,
104 UserNotFound = 7000,
109 UnsupportedPasswordType = 7001,
111 UserPasswordMismatch = 7002,
113 AuthHeaderNotFound = 7003,
115 InvalidAuthHeader = 7004,
117 AccessDenied = 7005,
119 PermissionDenied = 7006,
121 FlowAlreadyExists = 8000,
125 FlowNotFound = 8001,
126 TriggerAlreadyExists = 9000,
130 TriggerNotFound = 9001,
131 }
133
134impl StatusCode {
135 pub fn is_success(code: u32) -> bool {
137 Self::Success as u32 == code
138 }
139
140 pub fn should_log_error(&self) -> bool {
143 match self {
144 StatusCode::Unknown
145 | StatusCode::Unexpected
146 | StatusCode::Internal
147 | StatusCode::Cancelled
148 | StatusCode::DeadlineExceeded
149 | StatusCode::IllegalState
150 | StatusCode::EngineExecuteQuery
151 | StatusCode::StorageUnavailable
152 | StatusCode::RuntimeResourcesExhausted
153 | StatusCode::External => true,
154
155 StatusCode::PartialFailure
156 | StatusCode::Success
157 | StatusCode::Unsupported
158 | StatusCode::InvalidArguments
159 | StatusCode::InvalidSyntax
160 | StatusCode::TableAlreadyExists
161 | StatusCode::TableNotFound
162 | StatusCode::RegionAlreadyExists
163 | StatusCode::RegionNotFound
164 | StatusCode::PlanQuery
165 | StatusCode::FlowAlreadyExists
166 | StatusCode::FlowNotFound
167 | StatusCode::TriggerAlreadyExists
168 | StatusCode::TriggerNotFound
169 | StatusCode::RegionNotReady
170 | StatusCode::RegionBusy
171 | StatusCode::RegionReadonly
172 | StatusCode::TableColumnNotFound
173 | StatusCode::TableColumnExists
174 | StatusCode::DatabaseNotFound
175 | StatusCode::RateLimited
176 | StatusCode::UserNotFound
177 | StatusCode::TableUnavailable
178 | StatusCode::DatabaseAlreadyExists
179 | StatusCode::UnsupportedPasswordType
180 | StatusCode::UserPasswordMismatch
181 | StatusCode::AuthHeaderNotFound
182 | StatusCode::InvalidAuthHeader
183 | StatusCode::AccessDenied
184 | StatusCode::PermissionDenied
185 | StatusCode::RequestOutdated
186 | StatusCode::Suspended => false,
187 }
188 }
189
190 pub fn from_u32(value: u32) -> Option<Self> {
191 StatusCode::from_repr(value as usize)
192 }
193
194 pub fn serialize_as_u32<S>(code: &Self, serializer: S) -> Result<S::Ok, S::Error>
195 where
196 S: Serializer,
197 {
198 serializer.serialize_u32(*code as u32)
199 }
200
201 pub fn deserialize_from_u32<'de, D>(deserializer: D) -> Result<Self, D::Error>
202 where
203 D: Deserializer<'de>,
204 {
205 let code = u32::deserialize(deserializer)?;
206 StatusCode::from_u32(code)
207 .ok_or_else(|| serde::de::Error::custom(format!("unknown status code: {code}")))
208 }
209}
210
211impl fmt::Display for StatusCode {
212 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213 write!(f, "{self:?}")
215 }
216}
217
218#[macro_export]
219macro_rules! define_from_tonic_status {
220 ($Error: ty, $Variant: ident) => {
221 impl From<tonic::Status> for $Error {
222 fn from(e: tonic::Status) -> Self {
223 use snafu::location;
224
225 fn metadata_value(e: &tonic::Status, key: &str) -> Option<String> {
226 e.metadata()
227 .get(key)
228 .and_then(|v| String::from_utf8(v.as_bytes().to_vec()).ok())
229 }
230 let code = metadata_value(&e, $crate::GREPTIME_DB_HEADER_ERROR_CODE)
231 .and_then(|s| {
232 if let Ok(code) = s.parse::<u32>() {
233 StatusCode::from_u32(code)
234 } else {
235 None
236 }
237 })
238 .unwrap_or_else(|| match e.code() {
239 tonic::Code::Cancelled => StatusCode::Cancelled,
240 tonic::Code::DeadlineExceeded => StatusCode::DeadlineExceeded,
241 _ => StatusCode::Internal,
242 });
243
244 let msg = metadata_value(&e, $crate::GREPTIME_DB_HEADER_ERROR_MSG)
245 .unwrap_or_else(|| e.message().to_string());
246 let retry_hint = metadata_value(&e, $crate::GREPTIME_DB_HEADER_ERROR_RETRY_HINT)
247 .and_then(|s| s.parse().ok())
248 .unwrap_or($crate::ext::RetryHint::NonRetryable);
249
250 Self::$Variant {
252 code,
253 msg,
254 tonic_code: e.code(),
255 retry_hint,
256 location: location!(),
257 }
258 }
259 }
260 };
261}
262
263#[macro_export]
264macro_rules! define_into_tonic_status {
265 ($Error: ty) => {
266 impl From<$Error> for tonic::Status {
267 fn from(err: $Error) -> Self {
268 use tonic::codegen::http::{HeaderMap, HeaderValue};
269 use tonic::metadata::MetadataMap;
270 use $crate::{
271 GREPTIME_DB_HEADER_ERROR_CODE, GREPTIME_DB_HEADER_ERROR_RETRY_HINT,
272 };
273
274 common_telemetry::error!(err; "Failed to handle request");
275
276 let mut headers = HeaderMap::<HeaderValue>::with_capacity(3);
277
278 let status_code = err.status_code();
281 headers.insert(
282 GREPTIME_DB_HEADER_ERROR_CODE,
283 HeaderValue::from(status_code as u32),
284 );
285 headers.insert(
286 GREPTIME_DB_HEADER_ERROR_RETRY_HINT,
287 HeaderValue::from_static(err.retry_hint().as_str()),
288 );
289 let root_error = err.output_msg();
290
291 let metadata = MetadataMap::from_headers(headers);
292 tonic::Status::with_metadata(
293 $crate::status_code::status_to_tonic_code(status_code),
294 root_error,
295 metadata,
296 )
297 }
298 }
299 };
300}
301
302pub fn status_to_tonic_code(status_code: StatusCode) -> Code {
304 match status_code {
305 StatusCode::Success => Code::Ok,
306 StatusCode::Unknown | StatusCode::External | StatusCode::PartialFailure => Code::Unknown,
307 StatusCode::Unsupported => Code::Unimplemented,
308 StatusCode::Unexpected
309 | StatusCode::IllegalState
310 | StatusCode::Internal
311 | StatusCode::PlanQuery
312 | StatusCode::EngineExecuteQuery => Code::Internal,
313 StatusCode::InvalidArguments | StatusCode::InvalidSyntax | StatusCode::RequestOutdated => {
314 Code::InvalidArgument
315 }
316 StatusCode::Cancelled => Code::Cancelled,
317 StatusCode::DeadlineExceeded => Code::DeadlineExceeded,
318 StatusCode::TableAlreadyExists
319 | StatusCode::TableColumnExists
320 | StatusCode::RegionAlreadyExists
321 | StatusCode::DatabaseAlreadyExists
322 | StatusCode::TriggerAlreadyExists
323 | StatusCode::FlowAlreadyExists => Code::AlreadyExists,
324 StatusCode::TableNotFound
325 | StatusCode::RegionNotFound
326 | StatusCode::TableColumnNotFound
327 | StatusCode::DatabaseNotFound
328 | StatusCode::UserNotFound
329 | StatusCode::TriggerNotFound
330 | StatusCode::FlowNotFound => Code::NotFound,
331 StatusCode::TableUnavailable
332 | StatusCode::StorageUnavailable
333 | StatusCode::RegionNotReady => Code::Unavailable,
334 StatusCode::RuntimeResourcesExhausted
335 | StatusCode::RateLimited
336 | StatusCode::RegionBusy
337 | StatusCode::Suspended => Code::ResourceExhausted,
338 StatusCode::UnsupportedPasswordType
339 | StatusCode::UserPasswordMismatch
340 | StatusCode::AuthHeaderNotFound
341 | StatusCode::InvalidAuthHeader => Code::Unauthenticated,
342 StatusCode::AccessDenied | StatusCode::PermissionDenied | StatusCode::RegionReadonly => {
343 Code::PermissionDenied
344 }
345 }
346}
347
348#[cfg(test)]
349mod tests {
350 use strum::IntoEnumIterator;
351
352 use super::*;
353
354 fn assert_status_code_display(code: StatusCode, msg: &str) {
355 let code_msg = format!("{code}");
356 assert_eq!(msg, code_msg);
357 }
358
359 #[test]
360 fn test_display_status_code() {
361 assert_status_code_display(StatusCode::Unknown, "Unknown");
362 assert_status_code_display(StatusCode::TableAlreadyExists, "TableAlreadyExists");
363 }
364
365 #[test]
366 fn test_from_u32() {
367 for code in StatusCode::iter() {
368 let num = code as u32;
369 assert_eq!(StatusCode::from_u32(num), Some(code));
370 }
371
372 assert_eq!(StatusCode::from_u32(10000), None);
373 }
374
375 #[test]
376 fn test_is_success() {
377 assert!(StatusCode::is_success(0));
378 assert!(!StatusCode::is_success(1));
379 assert!(!StatusCode::is_success(2));
380 assert!(!StatusCode::is_success(3));
381 }
382}