BlueSky & more on desktop
lazurite.stormlightlabs.org/
tauri
rust
typescript
bluesky
appview
atproto
solid
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}