1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
use std::path::PathBuf;
use serde::Serialize;
use thiserror::Error;
/// Unified error type for Diaryx operations
///
/// Many of these are necessary because of the abstracted FileSystem in `fs.rs`.
#[derive(Debug, Error)]
pub enum DiaryxError {
/// Error for functionality that's intentionally not supported in the current mode/API.
///
/// Used during the async filesystem refactor for features that aren't yet migrated.
#[error("Unsupported operation: {0}")]
Unsupported(String),
/// General error for any kind of I/O issue not otherwise documented here.
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
/// A kind of error representing a failed file read.
///
/// Can occur due to:
/// - insufficient permissions
/// - locking/concurrent access
/// - resource issues
///
/// Diaryx should display an error message if a file cannot be read.
#[error("Failed to read file '{path}': {source}")]
FileRead {
/// Path to the file that failed to be read
path: PathBuf,
/// std::io error that caused this error
source: std::io::Error,
},
/// A kind of error representing a failed file write.
///
/// Can occur due to:
/// - insufficient permissions
/// - locking/concurrent access
/// - resource issues
///
/// Diaryx should display an error message if a file cannot be written.
#[error("Failed to write file '{path}': {source}")]
FileWrite {
/// Path to file that failed to be written
path: PathBuf,
/// std::io error that caused this error
source: std::io::Error,
},
/// An error that occured while serializing or deserializing YAML data from the frontmatter.
///
/// Inherited from `serde_yaml::Error`
#[error("YAML parsing error: {0}")]
Yaml(#[from] serde_yaml::Error),
/// YAML parsing error with file path context.
/// Use this when you know which file caused the YAML parse error.
#[error("YAML parsing error in '{path}': {message}")]
YamlParse {
/// Path to the file with the YAML error
path: PathBuf,
/// The YAML error message
message: String,
},
/// An error that occurs when no frontmatter is found in a file.
///
/// Diaryx should gracefully work around this error by doing things such as the following:
/// - If attempting to read frontmatter, simply display empty values for all possible.
/// - If trying to write to frontmatter, initialize it first by adding `---` delimiters before and after.
#[error("No frontmatter found in '{0}'")]
NoFrontmatter(PathBuf),
/// Error from invalid/unparseable frontmatter.
///
/// Diaryx should gracefully work around this error whenever possible.
#[error("Invalid frontmatter structure in '{0}'")]
InvalidFrontmatter(PathBuf),
/// Date errors
#[error(
"Invalid date format: '{0}'. Try 'today', 'yesterday', 'last friday', '3 days ago', or 'YYYY-MM-DD'"
)]
InvalidDateFormat(String),
/// Error that occurs when deserializing config.toml file.
///
/// Inherited from `toml::de::Error`
#[error("Config parse error: {0}")]
ConfigParse(#[from] toml::de::Error),
/// Config failed to serialize.
///
/// Inherited from `toml::ser::Error`.
#[error("Config serialize error: {0}")]
ConfigSerialize(#[from] toml::ser::Error),
/// Error indicating a failure to find config directory.
/// Diaryx should fall back to default config when this occurs.
#[error("Could not determine config directory")]
NoConfigDir,
/// Error from missing config.
#[error("Configuration not initialized. Run 'diaryx init' first.")]
ConfigNotInitialized,
/// Error when editor is not configured for `diaryx open` commands and similar.
/// This should be rare, because in Diaryx CLI it tries common editors like `nano` before returning this.
#[error("No editor found. Set $EDITOR, $VISUAL, or configure editor in config file")]
NoEditorFound,
/// Error from failing to launch an editor.
/// Common in WASI or other environments that don't support forking a process.
#[error("Failed to launch editor '{editor}': {source}")]
EditorLaunchFailed {
/// Name of editor command that failed
editor: String,
/// std::io error that caused this error
source: std::io::Error,
},
/// Error for when editor fails for some reason.
/// Should be passed onto the user.
#[error("Editor exited with code {0}")]
EditorExited(i32),
/// Error for when workspace is not found.
/// Should give an error message to user, then possibly fall back to default config.
#[error("Workspace not found at '{0}'")]
WorkspaceNotFound(PathBuf),
/// When creating a workspace, workspace already exists.
/// Should give a message to user.
#[error("Workspace already exists at '{0}'")]
WorkspaceAlreadyExists(PathBuf),
/// Error for when template is not defined.
/// Should give a message to the user.
#[error("Template not found: '{0}'")]
TemplateNotFound(String),
/// Error for when trying to create a template that already exists.
#[error("Template already exists: '{0}'")]
TemplateAlreadyExists(PathBuf),
/// Error for invalid path structure (e.g., missing parent directory or filename).
#[error("Invalid path '{path}': {message}")]
InvalidPath {
/// Path that is invalid
path: PathBuf,
/// Description of what's wrong with the path
message: String,
},
/// Error from CRDT operations (sync, storage, etc.)
#[cfg(feature = "crdt")]
#[error("CRDT error: {0}")]
Crdt(String),
/// Error from SQLite database operations
#[cfg(all(feature = "crdt-sqlite", not(target_arch = "wasm32")))]
#[error("Database error: {0}")]
Database(#[from] rusqlite::Error),
}
/// Result type alias for Diaryx operations
pub type Result<T> = std::result::Result<T, DiaryxError>;
/// A serializable representation of DiaryxError for IPC (e.g., Tauri)
#[derive(Debug, Clone, Serialize)]
pub struct SerializableError {
/// Error kind/variant name
pub kind: String,
/// Human-readable error message
pub message: String,
/// Associated path (if applicable)
pub path: Option<PathBuf>,
}
impl From<&DiaryxError> for SerializableError {
fn from(err: &DiaryxError) -> Self {
let kind = match err {
DiaryxError::Io(_) => "Io",
DiaryxError::FileRead { .. } => "FileRead",
DiaryxError::FileWrite { .. } => "FileWrite",
DiaryxError::Yaml(_) => "Yaml",
DiaryxError::YamlParse { .. } => "YamlParse",
DiaryxError::NoFrontmatter(_) => "NoFrontmatter",
DiaryxError::InvalidFrontmatter(_) => "InvalidFrontmatter",
DiaryxError::InvalidDateFormat(_) => "InvalidDateFormat",
DiaryxError::ConfigParse(_) => "ConfigParse",
DiaryxError::ConfigSerialize(_) => "ConfigSerialize",
DiaryxError::NoConfigDir => "NoConfigDir",
DiaryxError::ConfigNotInitialized => "ConfigNotInitialized",
DiaryxError::NoEditorFound => "NoEditorFound",
DiaryxError::EditorLaunchFailed { .. } => "EditorLaunchFailed",
DiaryxError::EditorExited(_) => "EditorExited",
DiaryxError::WorkspaceNotFound(_) => "WorkspaceNotFound",
DiaryxError::WorkspaceAlreadyExists(_) => "WorkspaceAlreadyExists",
DiaryxError::TemplateNotFound(_) => "TemplateNotFound",
DiaryxError::TemplateAlreadyExists(_) => "TemplateAlreadyExists",
DiaryxError::InvalidPath { .. } => "InvalidPath",
DiaryxError::Unsupported(_) => "Unsupported",
#[cfg(feature = "crdt")]
DiaryxError::Crdt(_) => "Crdt",
#[cfg(all(feature = "crdt-sqlite", not(target_arch = "wasm32")))]
DiaryxError::Database(_) => "Database",
}
.to_string();
let path = match err {
DiaryxError::FileRead { path, .. } => Some(path.clone()),
DiaryxError::FileWrite { path, .. } => Some(path.clone()),
DiaryxError::YamlParse { path, .. } => Some(path.clone()),
DiaryxError::NoFrontmatter(path) => Some(path.clone()),
DiaryxError::InvalidFrontmatter(path) => Some(path.clone()),
DiaryxError::WorkspaceNotFound(path) => Some(path.clone()),
DiaryxError::WorkspaceAlreadyExists(path) => Some(path.clone()),
DiaryxError::TemplateAlreadyExists(path) => Some(path.clone()),
DiaryxError::InvalidPath { path, .. } => Some(path.clone()),
_ => None,
};
Self {
kind,
message: err.to_string(),
path,
}
}
}
impl From<DiaryxError> for SerializableError {
fn from(err: DiaryxError) -> Self {
SerializableError::from(&err)
}
}
impl DiaryxError {
/// Convert to a serializable representation for IPC
pub fn to_serializable(&self) -> SerializableError {
SerializableError::from(self)
}
}