1use crate::log::log_def::CODE_STR;
2use crate::log_e;
3use crate::store::db::sql_def::DbError;
4use log::error;
5use std::backtrace::Backtrace;
6use std::fmt;
7
8#[repr(i32)]
9#[derive(Clone, Copy, Debug, PartialEq)]
10pub enum VibeEngineErrorCode {
12 Unknown = -1,
14
15 Success = 200,
17
18 EngineDropped = 100001,
20
21 DatabaseNotOpened = 100002,
23
24 DatabaseOpenFailed = 100003,
26 LogDatabaseOpenFailed = 100223,
28
29 WorkDatabaseOpenFailed = 132003,
31
32 DatabaseIOError = 100004,
34
35 DatabaseTargetNotFound = 100005,
37
38 DatabaseThreadError = 100006,
40
41 PostError = 100007,
43 Cancelled = 100008,
45
46 ParameterEmpty = 100012,
48 OAuthError = 100013,
50 ConfigError = 100014,
52
53 IOError = 100017,
55 BadRequest = 100018,
57 RequestError = 100019,
59 InternalServerError = 100020,
61 NetworkError = 100021,
63 UnsupportedError = 100022,
65
66 TimeoutError = 100023,
68 ConnectError = 100024,
70 TlsConnectError = 100025,
72 OptionsParseError = 100026,
74
75 SerdeDeserializeError = 100027,
77 SerdeSerializeError = 100028,
79
80 InvalidArgumentLogInfo = 100029,
82
83 MPSCSendError = 100037,
85
86 GenerateQRError = 10005,
88 RuntimeError = 10006,
90 BeaverError = 10007,
92 SocketRecvTimeout = 10008,
94 SocketClosed = 10009,
96 ProtocParseError = 10010,
98 SocketNotOpened = 10011,
100 TaskInterruptionError = 10012,
102
103 ConnectionClosed = 30001,
105
106 ConnectionExists = 34001,
108
109 ConnectionClosing = 30027,
111
112 InternalError = 32002,
114 NotLoggedInError = 32003,
116 PageTokenError = 32004,
118 ClipboardInitializeError = 32005,
120
121 NotSupportedYet = 999999,
123}
124
125impl VibeEngineErrorCode {
126 pub fn code(&self) -> i32 {
140 *self as i32
141 }
142}
143
144#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
145pub enum VibeErrorKind {
147 Config,
149 Runtime,
151 Task,
153 Database,
155 Network,
157 Log,
159 Unsupported,
161 Internal,
163 Unknown,
165}
166
167impl VibeErrorKind {
168 fn from_code(code: VibeEngineErrorCode) -> Self {
169 match code {
170 VibeEngineErrorCode::ConfigError
171 | VibeEngineErrorCode::ParameterEmpty
172 | VibeEngineErrorCode::InvalidArgumentLogInfo => Self::Config,
173 VibeEngineErrorCode::RuntimeError
174 | VibeEngineErrorCode::EngineDropped
175 | VibeEngineErrorCode::MPSCSendError => Self::Runtime,
176 VibeEngineErrorCode::PostError
177 | VibeEngineErrorCode::TaskInterruptionError
178 | VibeEngineErrorCode::Cancelled => Self::Task,
179 VibeEngineErrorCode::DatabaseNotOpened
180 | VibeEngineErrorCode::DatabaseOpenFailed
181 | VibeEngineErrorCode::LogDatabaseOpenFailed
182 | VibeEngineErrorCode::WorkDatabaseOpenFailed
183 | VibeEngineErrorCode::DatabaseIOError
184 | VibeEngineErrorCode::DatabaseTargetNotFound
185 | VibeEngineErrorCode::DatabaseThreadError => Self::Database,
186 VibeEngineErrorCode::NetworkError
187 | VibeEngineErrorCode::BadRequest
188 | VibeEngineErrorCode::RequestError
189 | VibeEngineErrorCode::InternalServerError
190 | VibeEngineErrorCode::TimeoutError
191 | VibeEngineErrorCode::ConnectError
192 | VibeEngineErrorCode::TlsConnectError
193 | VibeEngineErrorCode::SocketRecvTimeout
194 | VibeEngineErrorCode::SocketClosed
195 | VibeEngineErrorCode::SocketNotOpened
196 | VibeEngineErrorCode::ConnectionClosed
197 | VibeEngineErrorCode::ConnectionExists
198 | VibeEngineErrorCode::ConnectionClosing => Self::Network,
199 VibeEngineErrorCode::UnsupportedError | VibeEngineErrorCode::NotSupportedYet => {
200 Self::Unsupported
201 }
202 VibeEngineErrorCode::InternalError
203 | VibeEngineErrorCode::IOError
204 | VibeEngineErrorCode::SerdeDeserializeError
205 | VibeEngineErrorCode::SerdeSerializeError
206 | VibeEngineErrorCode::OptionsParseError
207 | VibeEngineErrorCode::GenerateQRError
208 | VibeEngineErrorCode::BeaverError
209 | VibeEngineErrorCode::ProtocParseError
210 | VibeEngineErrorCode::OAuthError
211 | VibeEngineErrorCode::NotLoggedInError
212 | VibeEngineErrorCode::PageTokenError
213 | VibeEngineErrorCode::ClipboardInitializeError => Self::Internal,
214 VibeEngineErrorCode::Success | VibeEngineErrorCode::Unknown => Self::Unknown,
215 }
216 }
217}
218
219#[derive(Debug, Clone)]
220pub struct VibeEngineError {
222 code: i32,
223 kind: VibeErrorKind,
224 message: String,
225 source: Option<String>,
226 context: Vec<String>,
227}
228
229impl fmt::Display for VibeEngineError {
230 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231 write!(
232 f,
233 "vibe-ready error [{:?}/{}]: {}",
234 self.kind, self.code, self.message
235 )?;
236 if let Some(source) = &self.source {
237 write!(f, "; source: {source}")?;
238 }
239 if !self.context.is_empty() {
240 write!(f, "; context: {}", self.context.join("; "))?;
241 }
242 Ok(())
243 }
244}
245
246impl std::error::Error for VibeEngineError {}
247
248impl From<DbError> for VibeEngineError {
249 fn from(err: DbError) -> Self {
250 error!("DbError: {:?}, {:?}", err, Backtrace::capture());
251 let db_error = err.clone();
252 let mut error = match err {
253 DbError::OpenFailed => {
254 VibeEngineError::from_code(VibeEngineErrorCode::DatabaseOpenFailed)
255 }
256 DbError::DatabaseIOError => {
257 VibeEngineError::from_code(VibeEngineErrorCode::DatabaseIOError)
258 }
259 DbError::NotOpen => VibeEngineError::from_code(VibeEngineErrorCode::DatabaseOpenFailed),
260 DbError::TargetNotFound => {
261 VibeEngineError::from_code(VibeEngineErrorCode::DatabaseTargetNotFound)
262 }
263 DbError::DatabaseThreadError => {
264 VibeEngineError::from_code(VibeEngineErrorCode::DatabaseThreadError)
265 }
266 DbError::DatabaseUnlockError => {
267 VibeEngineError::from_code(VibeEngineErrorCode::DatabaseOpenFailed)
268 }
269 DbError::NotSupportedYet => {
270 VibeEngineError::from_code(VibeEngineErrorCode::DatabaseIOError)
271 }
272 DbError::JoinError => VibeEngineError::from_code(VibeEngineErrorCode::DatabaseIOError),
273 _ => VibeEngineError::from_code(VibeEngineErrorCode::Unknown),
274 };
275 error.source = Some(format!("{db_error:?}"));
276 error
277 }
278}
279
280impl VibeEngineError {
281 pub fn code(&self) -> i32 {
287 self.code
288 }
289
290 pub fn kind(&self) -> VibeErrorKind {
296 self.kind
297 }
298
299 pub fn message(&self) -> &str {
305 &self.message
306 }
307
308 pub fn source_message(&self) -> Option<&str> {
314 self.source.as_deref()
315 }
316
317 pub fn context(&self) -> &[String] {
323 &self.context
324 }
325
326 pub fn with_source(mut self, source: impl Into<String>) -> Self {
342 self.source = Some(source.into());
343 self
344 }
345
346 pub fn with_context(mut self, context: impl Into<String>) -> Self {
352 self.context.push(context.into());
353 self
354 }
355
356 pub fn from_success() -> Self {
362 Self::from_error_code(VibeEngineErrorCode::Success)
363 }
364
365 pub fn from_u16(code: u16) -> Self {
371 VibeEngineError::from_raw_code(code as i32)
372 }
373
374 pub fn from_u32(code: u32) -> Self {
380 VibeEngineError::from_raw_code(code as i32)
381 }
382
383 pub fn is_success(&self) -> bool {
389 self.code == VibeEngineErrorCode::Success.code()
390 }
391
392 pub fn same_code(&self) -> Self {
398 self.clone()
399 }
400
401 pub fn from_error_code(value: VibeEngineErrorCode) -> Self {
407 VibeEngineError {
408 code: value.code(),
409 kind: VibeErrorKind::from_code(value),
410 message: default_message(value).to_string(),
411 source: None,
412 context: Vec::new(),
413 }
414 }
415
416 pub fn from_code(value: VibeEngineErrorCode) -> Self {
422 Self::from_error_code(value)
423 }
424
425 pub fn from_error_code_msg(value: VibeEngineErrorCode, msg: String) -> Self {
431 VibeEngineError {
432 message: msg,
433 ..Self::from_error_code(value)
434 }
435 }
436
437 pub fn from_task_interruption_error() -> Self {
443 Self::from_error_code(VibeEngineErrorCode::TaskInterruptionError)
444 }
445
446 pub fn from_internal_error() -> Self {
452 Self::from_error_code(VibeEngineErrorCode::InternalError)
453 }
454
455 pub fn from_mpsc_send_error() -> Self {
461 Self::from_error_code(VibeEngineErrorCode::MPSCSendError)
462 }
463
464 pub fn from_parameter_empty() -> Self {
470 Self::from_error_code(VibeEngineErrorCode::ParameterEmpty)
471 }
472 pub fn from_parameter_empty_log(tag: &str) -> Self {
478 let code = VibeEngineErrorCode::ParameterEmpty.code();
479 log_e!(tag, CODE_STR, code);
480 Self::from_error_code(VibeEngineErrorCode::ParameterEmpty)
481 }
482
483 fn from_raw_code(code: i32) -> Self {
484 VibeEngineError {
485 code,
486 kind: VibeErrorKind::Unknown,
487 message: format!("unknown error code {code}"),
488 source: None,
489 context: Vec::new(),
490 }
491 }
492}
493
494fn default_message(code: VibeEngineErrorCode) -> &'static str {
495 match code {
496 VibeEngineErrorCode::Unknown => "unknown error",
497 VibeEngineErrorCode::Success => "success",
498 VibeEngineErrorCode::EngineDropped => "engine has been dropped",
499 VibeEngineErrorCode::DatabaseNotOpened => "database is not opened",
500 VibeEngineErrorCode::DatabaseOpenFailed => "database open failed",
501 VibeEngineErrorCode::LogDatabaseOpenFailed => "log database open failed",
502 VibeEngineErrorCode::WorkDatabaseOpenFailed => "work database open failed",
503 VibeEngineErrorCode::DatabaseIOError => "database I/O error",
504 VibeEngineErrorCode::DatabaseTargetNotFound => "database target not found",
505 VibeEngineErrorCode::DatabaseThreadError => "database worker thread error",
506 VibeEngineErrorCode::PostError => "task post failed",
507 VibeEngineErrorCode::Cancelled => "task was cancelled",
508 VibeEngineErrorCode::ParameterEmpty => "required parameter is empty",
509 VibeEngineErrorCode::OAuthError => "OAuth error",
510 VibeEngineErrorCode::ConfigError => "configuration error",
511 VibeEngineErrorCode::IOError => "I/O error",
512 VibeEngineErrorCode::BadRequest => "bad request",
513 VibeEngineErrorCode::RequestError => "request error",
514 VibeEngineErrorCode::InternalServerError => "internal server error",
515 VibeEngineErrorCode::NetworkError => "network error",
516 VibeEngineErrorCode::UnsupportedError => "unsupported operation",
517 VibeEngineErrorCode::TimeoutError => "operation timed out",
518 VibeEngineErrorCode::ConnectError => "connection failed",
519 VibeEngineErrorCode::TlsConnectError => "TLS connection failed",
520 VibeEngineErrorCode::OptionsParseError => "options parse failed",
521 VibeEngineErrorCode::SerdeDeserializeError => "deserialization failed",
522 VibeEngineErrorCode::SerdeSerializeError => "serialization failed",
523 VibeEngineErrorCode::InvalidArgumentLogInfo => "invalid log info argument",
524 VibeEngineErrorCode::MPSCSendError => "channel send failed",
525 VibeEngineErrorCode::GenerateQRError => "QR generation failed",
526 VibeEngineErrorCode::RuntimeError => "runtime error",
527 VibeEngineErrorCode::BeaverError => "internal beaver error",
528 VibeEngineErrorCode::SocketRecvTimeout => "socket receive timed out",
529 VibeEngineErrorCode::SocketClosed => "socket closed",
530 VibeEngineErrorCode::ProtocParseError => "protocol parse failed",
531 VibeEngineErrorCode::SocketNotOpened => "socket is not opened",
532 VibeEngineErrorCode::TaskInterruptionError => "task interrupted",
533 VibeEngineErrorCode::ConnectionClosed => "connection closed",
534 VibeEngineErrorCode::ConnectionExists => "connection already exists",
535 VibeEngineErrorCode::ConnectionClosing => "connection is closing",
536 VibeEngineErrorCode::InternalError => "internal error",
537 VibeEngineErrorCode::NotLoggedInError => "not logged in",
538 VibeEngineErrorCode::PageTokenError => "page token error",
539 VibeEngineErrorCode::ClipboardInitializeError => "clipboard initialization failed",
540 VibeEngineErrorCode::NotSupportedYet => "not supported yet",
541 }
542}
543
544impl serde::Serialize for VibeEngineError {
545 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
546 where
547 S: serde::Serializer,
548 {
549 use serde::ser::SerializeStruct;
550 let mut s = serializer.serialize_struct("VibeEngineError", 5)?;
551 s.serialize_field("code", &self.code())?;
552 s.serialize_field("kind", &self.kind)?;
553 s.serialize_field("message", &self.message)?;
554 s.serialize_field("source", &self.source)?;
555 s.serialize_field("context", &self.context)?;
556 s.end()
557 }
558}
559
560#[cfg(test)]
561mod tests {
562 use super::*;
563
564 fn assert_std_error<T: std::error::Error>() {}
565
566 #[test]
567 fn error_model_exposes_kind_message_source_and_context() {
568 assert_std_error::<VibeEngineError>();
569
570 let error = VibeEngineError::from_error_code_msg(
571 VibeEngineErrorCode::TimeoutError,
572 "destroy timed out".to_string(),
573 )
574 .with_source("shutdown queue did not finish")
575 .with_context("engine.destroy_with_timeout");
576
577 assert_eq!(error.code(), VibeEngineErrorCode::TimeoutError.code());
578 assert_eq!(error.kind(), VibeErrorKind::Network);
579 assert_eq!(error.message(), "destroy timed out");
580 assert_eq!(
581 error.source_message(),
582 Some("shutdown queue did not finish")
583 );
584 assert_eq!(
585 error.context(),
586 &["engine.destroy_with_timeout".to_string()]
587 );
588 assert!(error.to_string().contains("destroy timed out"));
589 }
590}
591
592#[cfg(test)]
593mod strict_tests {
594 use super::*;
595 include!(concat!(
596 env!("CARGO_MANIFEST_DIR"),
597 "/test/unit/api/engine_error_tests.rs"
598 ));
599}