1use std::io;
12use std::path::PathBuf;
13
14use thiserror::Error;
15
16use crate::query::QueryParseError;
17
18pub type Result<T> = std::result::Result<T, LogdiveError>;
20
21#[derive(Debug, Error)]
23#[non_exhaustive]
24pub enum LogdiveError {
25 #[error(transparent)]
28 QueryParse(#[from] QueryParseError),
29
30 #[error("invalid datetime {input:?}: {reason}")]
33 InvalidDatetime { input: String, reason: String },
34
35 #[error("unsafe field name {0:?}")]
39 UnsafeFieldName(String),
40
41 #[error("corrupt fields JSON in row: {0}")]
44 CorruptFieldsJson(#[source] serde_json::Error),
45
46 #[error("sqlite error: {0}")]
48 Sqlite(#[from] rusqlite::Error),
49
50 #[error("io error at {path}: {source}")]
53 Io {
54 path: PathBuf,
55 #[source]
56 source: io::Error,
57 },
58
59 #[error("io error: {0}")]
62 IoBare(#[from] io::Error),
63
64 #[error("json error: {0}")]
66 Json(#[from] serde_json::Error),
67}
68
69impl LogdiveError {
70 pub fn io_at(path: impl Into<PathBuf>, source: io::Error) -> Self {
74 Self::Io {
75 path: path.into(),
76 source,
77 }
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 #[test]
86 fn query_parse_error_converts_via_question_mark() {
87 fn try_parse() -> Result<()> {
91 let ast = crate::query::parse("level=")?; let _ = ast;
93 Ok(())
94 }
95 let err = try_parse().unwrap_err();
96 assert!(matches!(err, LogdiveError::QueryParse(_)));
97 }
98
99 #[test]
100 fn sqlite_error_converts_via_question_mark() {
101 fn do_thing() -> Result<()> {
102 let conn = rusqlite::Connection::open_in_memory()?;
103 conn.execute("this is not valid SQL", [])?;
104 Ok(())
105 }
106 let err = do_thing().unwrap_err();
107 assert!(matches!(err, LogdiveError::Sqlite(_)));
108 }
109
110 #[test]
111 fn json_error_converts_via_question_mark() {
112 fn do_thing() -> Result<serde_json::Value> {
113 let v = serde_json::from_str("not json")?;
114 Ok(v)
115 }
116 let err = do_thing().unwrap_err();
117 assert!(matches!(err, LogdiveError::Json(_)));
118 }
119
120 #[test]
121 fn io_at_attaches_path_to_error_message() {
122 let src = io::Error::new(io::ErrorKind::NotFound, "missing");
123 let err = LogdiveError::io_at("/tmp/never-exists.db", src);
124 let msg = format!("{err}");
125 assert!(msg.contains("/tmp/never-exists.db"));
126 assert!(msg.contains("missing"));
127 }
128
129 #[test]
130 fn invalid_datetime_formats_both_input_and_reason() {
131 let err = LogdiveError::InvalidDatetime {
132 input: "not-a-date".to_string(),
133 reason: "expected RFC3339".to_string(),
134 };
135 let msg = format!("{err}");
136 assert!(msg.contains("not-a-date"));
137 assert!(msg.contains("RFC3339"));
138 }
139}