BlueSky & more on desktop lazurite.stormlightlabs.org/
tauri rust typescript bluesky appview atproto solid
at main 135 lines 3.8 kB view raw
1use tauri_plugin_log::log; 2 3pub type Result<T> = std::result::Result<T, AppError>; 4 5fn log_chain_with_level(level: log::Level, context: &str, error: &impl std::error::Error) { 6 log::log!(level, "{context}: {error}"); 7 log::log!(level, "{context} debug: {error:?}"); 8 9 let mut source = error.source(); 10 let mut depth = 0; 11 12 while let Some(cause) = source { 13 depth += 1; 14 log::log!(level, "{context} cause[{depth}]: {cause}"); 15 source = cause.source(); 16 } 17} 18 19pub fn log_error_chain(context: &str, error: &impl std::error::Error) { 20 log_chain_with_level(log::Level::Error, context, error); 21} 22 23pub fn log_warn_chain(context: &str, error: &impl std::error::Error) { 24 log_chain_with_level(log::Level::Warn, context, error); 25} 26 27#[derive(Debug, Clone, Copy, PartialEq, Eq)] 28pub enum TypeaheadFetchErrorKind { 29 Decode, 30 Status(reqwest::StatusCode), 31 Transport, 32} 33 34#[derive(Debug, thiserror::Error)] 35#[error("{message}")] 36pub struct TypeaheadFetchError { 37 pub kind: TypeaheadFetchErrorKind, 38 pub message: String, 39} 40 41impl TypeaheadFetchError { 42 pub fn decode(error: &reqwest::Error) -> Self { 43 Self { 44 kind: TypeaheadFetchErrorKind::Decode, 45 message: format!("failed to decode typeahead response: {error}"), 46 } 47 } 48 49 pub fn status(status: reqwest::StatusCode) -> Self { 50 Self { 51 kind: TypeaheadFetchErrorKind::Status(status), 52 message: format!("typeahead endpoint returned {}", status.as_u16()), 53 } 54 } 55 56 pub fn transport(error: &reqwest::Error) -> Self { 57 Self { 58 kind: TypeaheadFetchErrorKind::Transport, 59 message: format!("failed to reach typeahead endpoint: {error}"), 60 } 61 } 62} 63 64#[derive(Debug, thiserror::Error)] 65pub enum AppError { 66 #[error("database error: {0}")] 67 Database(#[from] rusqlite::Error), 68 69 #[error("auth flow error: {0}")] 70 OAuth(#[from] jacquard::oauth::error::OAuthError), 71 72 #[error("session error: {0}")] 73 OAuthSession(#[from] jacquard::oauth::session::Error), 74 75 #[error("session store error: {0}")] 76 SessionStore(#[from] jacquard::common::session::SessionStoreError), 77 78 #[error("io error: {0}")] 79 Io(#[from] std::io::Error), 80 81 #[error("serialization error: {0}")] 82 SerdeJson(#[from] serde_json::Error), 83 84 #[error("http error: {0}")] 85 Http(#[from] reqwest::Error), 86 87 #[error("invalid atproto identifier: {0}")] 88 AtIdentifier(#[from] jacquard::types::string::AtStrError), 89 90 #[error("uri parse error: {0}")] 91 UriParse(#[from] jacquard::deps::fluent_uri::ParseError), 92 93 #[error("tauri error: {0}")] 94 Tauri(#[from] tauri::Error), 95 96 #[error("deep-link error: {0}")] 97 DeepLink(#[from] tauri_plugin_deep_link::Error), 98 99 #[error("path resolution failed: {0}")] 100 PathResolve(String), 101 102 #[error("state lock poisoned: {0}")] 103 StatePoisoned(&'static str), 104 105 #[error("{0}")] 106 Validation(String), 107} 108 109impl serde::Serialize for AppError { 110 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> 111 where 112 S: serde::ser::Serializer, 113 { 114 serializer.serialize_str(&self.to_string()) 115 } 116} 117 118impl AppError { 119 pub fn validation(msg: impl Into<String>) -> Self { 120 let msg = msg.into(); 121 log::error!("validation error: {}", &msg); 122 AppError::Validation(msg) 123 } 124 125 pub fn state_poisoned(msg: impl Into<String>) -> Self { 126 let msg = msg.into(); 127 log::error!("state lock poisoned: {}", msg); 128 AppError::StatePoisoned(Box::leak(msg.into_boxed_str())) 129 } 130 131 pub fn diagnostics(message: &'static str, error: impl std::fmt::Display) -> Self { 132 log::error!("{message} {error}"); 133 AppError::validation(message) 134 } 135}