1use thiserror::Error;
15
16pub mod codes {
20 pub const VALIDATION_ERROR: &str = "VALIDATION_ERROR";
22 pub const NOT_FOUND: &str = "NOT_FOUND";
24 pub const SCHEMA_ERROR: &str = "SCHEMA_ERROR";
26 pub const STORAGE_ERROR: &str = "STORAGE_ERROR";
28 pub const IO_ERROR: &str = "IO_ERROR";
30 pub const CONFIG_ERROR: &str = "CONFIG_ERROR";
32 pub const TABLE_NOT_FOUND: &str = "TABLE_NOT_FOUND";
34 pub const TABLE_REQUIRED: &str = "TABLE_REQUIRED";
39 pub const SCHEMA_EXISTS: &str = "SCHEMA_EXISTS";
42 pub const BACKUP_ERROR: &str = "BACKUP_ERROR";
44 pub const BATCH_ABORTED: &str = "BATCH_ABORTED";
46 pub const SNAPSHOT_ERROR: &str = "SNAPSHOT_ERROR";
48 pub const MATERIALIZE_DEST_RELATIVE: &str = "MATERIALIZE_DEST_RELATIVE";
50 pub const MATERIALIZE_DEST_INVALID: &str = "MATERIALIZE_DEST_INVALID";
52 pub const MATERIALIZE_IO_ERROR: &str = "MATERIALIZE_IO_ERROR";
54 pub const MATERIALIZE_SHA256_ERROR: &str = "MATERIALIZE_SHA256_ERROR";
56 pub const MATERIALIZE_ROW_NOT_FOUND: &str = "MATERIALIZE_ROW_NOT_FOUND";
58 pub const MATERIALIZE_EMPTY_RESULT: &str = "MATERIALIZE_EMPTY_RESULT";
60 pub const MATERIALIZE_FORMAT_ERROR: &str = "MATERIALIZE_FORMAT_ERROR";
62 pub const MATERIALIZE_FIELD_UNKNOWN: &str = "MATERIALIZE_FIELD_UNKNOWN";
64 pub const MATERIALIZE_INVALID_PARAM: &str = "MATERIALIZE_INVALID_PARAM";
66 pub const ALIAS_NOT_FOUND: &str = "ALIAS_NOT_FOUND";
68 pub const ALIAS_ALREADY_EXISTS: &str = "ALIAS_ALREADY_EXISTS";
71 pub const ALIAS_PARAMS_REQUIRED: &str = "ALIAS_PARAMS_REQUIRED";
74 pub const ALIAS_TEMPLATE_ERROR: &str = "ALIAS_TEMPLATE_ERROR";
77 pub const AMBIGUOUS_ID: &str = "AMBIGUOUS_ID";
80 pub const AGGREGATOR_ERROR: &str = "AGGREGATOR_ERROR";
86}
87
88#[derive(Error, Debug)]
90pub enum MiniAppError {
91 #[error("validation error on field '{field}': {reason}")]
93 Validation { field: String, reason: String },
94
95 #[error("row not found: {id}")]
97 NotFound { id: String },
98
99 #[error("schema parse error: {0}")]
101 Schema(String),
102
103 #[error("storage error: {0}")]
105 Storage(#[from] rusqlite::Error),
106
107 #[error("io error: {0}")]
109 Io(#[from] std::io::Error),
110
111 #[error("config error: {0}")]
113 Config(String),
114
115 #[error("table not found: {table}")]
117 TableNotFound { table: String },
118
119 #[error("table argument is required in multi-table mode")]
121 TableRequired,
122
123 #[error("schema already exists: {table}")]
125 SchemaExists { table: String },
126
127 #[error("backup error: {0}")]
129 Backup(String),
130
131 #[error("snapshot error: {0}")]
133 Snapshot(String),
134
135 #[error("batch aborted at op #{op_index}: {reason}")]
137 BatchAborted { op_index: usize, reason: String },
138
139 #[error("materialize dest must be absolute: {path}")]
141 MaterializeDestRelative { path: String },
142
143 #[error("materialize dest invalid '{path}': {reason}")]
145 MaterializeDestInvalid { path: String, reason: String },
146
147 #[error("materialize io error: {0}")]
149 MaterializeIo(String),
150
151 #[error("materialize sha256 error: {0}")]
153 MaterializeSha256(String),
154
155 #[error("materialize row not found: {id}")]
157 MaterializeRowNotFound { id: String },
158
159 #[error("materialize filter matched zero rows")]
161 MaterializeEmptyResult,
162
163 #[error("materialize format error: {0}")]
165 MaterializeFormatError(String),
166
167 #[error("materialize unknown field: {field}")]
169 MaterializeFieldUnknown { field: String },
170
171 #[error("materialize invalid param '{field}': {reason}")]
173 MaterializeInvalidParam { field: String, reason: String },
174
175 #[error("alias not found: {name}")]
177 AliasNotFound { name: String },
178
179 #[error("alias already exists: {name}")]
181 AliasAlreadyExists { name: String },
182
183 #[error("alias '{name}' requires params but none were provided")]
186 AliasParamsRequired { name: String },
187
188 #[error("alias template render error: {0}")]
190 AliasTemplateError(String),
191
192 #[error("ambiguous id prefix '{id_prefix}': {n} candidates", n = candidates.len())]
194 AmbiguousId {
195 id_prefix: String,
196 candidates: Vec<String>,
197 },
198
199 #[error("aggregator error: {0}")]
202 Aggregator(String),
203}
204
205impl MiniAppError {
206 pub fn code(&self) -> &'static str {
208 match self {
209 MiniAppError::Validation { .. } => codes::VALIDATION_ERROR,
210 MiniAppError::NotFound { .. } => codes::NOT_FOUND,
211 MiniAppError::Schema(_) => codes::SCHEMA_ERROR,
212 MiniAppError::Storage(_) => codes::STORAGE_ERROR,
213 MiniAppError::Io(_) => codes::IO_ERROR,
214 MiniAppError::Config(_) => codes::CONFIG_ERROR,
215 MiniAppError::TableNotFound { .. } => codes::TABLE_NOT_FOUND,
216 MiniAppError::TableRequired => codes::TABLE_REQUIRED,
217 MiniAppError::SchemaExists { .. } => codes::SCHEMA_EXISTS,
218 MiniAppError::Backup(_) => codes::BACKUP_ERROR,
219 MiniAppError::Snapshot(_) => codes::SNAPSHOT_ERROR,
220 MiniAppError::BatchAborted { .. } => codes::BATCH_ABORTED,
221 MiniAppError::MaterializeDestRelative { .. } => codes::MATERIALIZE_DEST_RELATIVE,
222 MiniAppError::MaterializeDestInvalid { .. } => codes::MATERIALIZE_DEST_INVALID,
223 MiniAppError::MaterializeIo(_) => codes::MATERIALIZE_IO_ERROR,
224 MiniAppError::MaterializeSha256(_) => codes::MATERIALIZE_SHA256_ERROR,
225 MiniAppError::MaterializeRowNotFound { .. } => codes::MATERIALIZE_ROW_NOT_FOUND,
226 MiniAppError::MaterializeEmptyResult => codes::MATERIALIZE_EMPTY_RESULT,
227 MiniAppError::MaterializeFormatError(_) => codes::MATERIALIZE_FORMAT_ERROR,
228 MiniAppError::MaterializeFieldUnknown { .. } => codes::MATERIALIZE_FIELD_UNKNOWN,
229 MiniAppError::MaterializeInvalidParam { .. } => codes::MATERIALIZE_INVALID_PARAM,
230 MiniAppError::AliasNotFound { .. } => codes::ALIAS_NOT_FOUND,
231 MiniAppError::AliasAlreadyExists { .. } => codes::ALIAS_ALREADY_EXISTS,
232 MiniAppError::AliasParamsRequired { .. } => codes::ALIAS_PARAMS_REQUIRED,
233 MiniAppError::AliasTemplateError(_) => codes::ALIAS_TEMPLATE_ERROR,
234 MiniAppError::AmbiguousId { .. } => codes::AMBIGUOUS_ID,
235 MiniAppError::Aggregator(_) => codes::AGGREGATOR_ERROR,
236 }
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 #[test]
245 fn error_code_all_variants() {
246 let cases: Vec<(&str, MiniAppError)> = vec![
247 (
248 codes::VALIDATION_ERROR,
249 MiniAppError::Validation {
250 field: "f".into(),
251 reason: "r".into(),
252 },
253 ),
254 (codes::NOT_FOUND, MiniAppError::NotFound { id: "x".into() }),
255 (codes::SCHEMA_ERROR, MiniAppError::Schema("s".into())),
256 (
257 codes::IO_ERROR,
258 MiniAppError::Io(std::io::Error::other("e")),
259 ),
260 (codes::CONFIG_ERROR, MiniAppError::Config("c".into())),
261 (
262 codes::TABLE_NOT_FOUND,
263 MiniAppError::TableNotFound { table: "t".into() },
264 ),
265 (codes::TABLE_REQUIRED, MiniAppError::TableRequired),
266 (
267 codes::SCHEMA_EXISTS,
268 MiniAppError::SchemaExists {
269 table: "my_table".into(),
270 },
271 ),
272 (
273 codes::BACKUP_ERROR,
274 MiniAppError::Backup("disk full".into()),
275 ),
276 (
277 codes::SNAPSHOT_ERROR,
278 MiniAppError::Snapshot("snapshot failed".into()),
279 ),
280 (
281 codes::BATCH_ABORTED,
282 MiniAppError::BatchAborted {
283 op_index: 2,
284 reason: "schema not found".into(),
285 },
286 ),
287 (
288 codes::MATERIALIZE_DEST_RELATIVE,
289 MiniAppError::MaterializeDestRelative {
290 path: "relative/path".into(),
291 },
292 ),
293 (
294 codes::MATERIALIZE_DEST_INVALID,
295 MiniAppError::MaterializeDestInvalid {
296 path: "/bad/path".into(),
297 reason: "parent dir not writable".into(),
298 },
299 ),
300 (
301 codes::MATERIALIZE_IO_ERROR,
302 MiniAppError::MaterializeIo("write failed".into()),
303 ),
304 (
305 codes::MATERIALIZE_SHA256_ERROR,
306 MiniAppError::MaterializeSha256("task panicked".into()),
307 ),
308 (
309 codes::MATERIALIZE_ROW_NOT_FOUND,
310 MiniAppError::MaterializeRowNotFound { id: "row-1".into() },
311 ),
312 (
313 codes::MATERIALIZE_EMPTY_RESULT,
314 MiniAppError::MaterializeEmptyResult,
315 ),
316 (
317 codes::MATERIALIZE_FORMAT_ERROR,
318 MiniAppError::MaterializeFormatError("yaml error".into()),
319 ),
320 (
321 codes::MATERIALIZE_FIELD_UNKNOWN,
322 MiniAppError::MaterializeFieldUnknown {
323 field: "unknown_field".into(),
324 },
325 ),
326 (
327 codes::MATERIALIZE_INVALID_PARAM,
328 MiniAppError::MaterializeInvalidParam {
329 field: "concat".into(),
330 reason: "concat=true requires ByFilter selector".into(),
331 },
332 ),
333 (
334 codes::ALIAS_NOT_FOUND,
335 MiniAppError::AliasNotFound {
336 name: "my_alias".into(),
337 },
338 ),
339 (
340 codes::ALIAS_ALREADY_EXISTS,
341 MiniAppError::AliasAlreadyExists {
342 name: "my_alias".into(),
343 },
344 ),
345 (
346 codes::ALIAS_PARAMS_REQUIRED,
347 MiniAppError::AliasParamsRequired {
348 name: "my_alias".into(),
349 },
350 ),
351 (
352 codes::ALIAS_TEMPLATE_ERROR,
353 MiniAppError::AliasTemplateError("template syntax error".into()),
354 ),
355 (
356 codes::AMBIGUOUS_ID,
357 MiniAppError::AmbiguousId {
358 id_prefix: "abc".into(),
359 candidates: vec!["abc-1".into(), "abc-2".into()],
360 },
361 ),
362 (
363 codes::AGGREGATOR_ERROR,
364 MiniAppError::Aggregator("empty sources".into()),
365 ),
366 ];
367 for (expected_code, err) in cases {
368 assert_eq!(
369 err.code(),
370 expected_code,
371 "wrong code for variant containing code {}",
372 expected_code
373 );
374 }
375 }
376
377 #[test]
378 fn backup_error_code_is_not_storage_or_io() {
379 let err = MiniAppError::Backup("some rusqlite error".to_string());
380 assert_eq!(err.code(), codes::BACKUP_ERROR);
381 assert_ne!(err.code(), codes::STORAGE_ERROR);
382 assert_ne!(err.code(), codes::IO_ERROR);
383 }
384}