use std::io;
use std::path::PathBuf;
use thiserror::Error;
use crate::query::QueryParseError;
pub type Result<T> = std::result::Result<T, LogdiveError>;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum LogdiveError {
#[error(transparent)]
QueryParse(#[from] QueryParseError),
#[error("invalid datetime {input:?}: {reason}")]
InvalidDatetime { input: String, reason: String },
#[error("unsafe field name {0:?}")]
UnsafeFieldName(String),
#[error("corrupt fields JSON in row: {0}")]
CorruptFieldsJson(#[source] serde_json::Error),
#[error("sqlite error: {0}")]
Sqlite(#[from] rusqlite::Error),
#[error("io error at {path}: {source}")]
Io {
path: PathBuf,
#[source]
source: io::Error,
},
#[error("io error: {0}")]
IoBare(#[from] io::Error),
#[error("json error: {0}")]
Json(#[from] serde_json::Error),
}
impl LogdiveError {
pub fn io_at(path: impl Into<PathBuf>, source: io::Error) -> Self {
Self::Io {
path: path.into(),
source,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn query_parse_error_converts_via_question_mark() {
fn try_parse() -> Result<()> {
let ast = crate::query::parse("level=")?; let _ = ast;
Ok(())
}
let err = try_parse().unwrap_err();
assert!(matches!(err, LogdiveError::QueryParse(_)));
}
#[test]
fn sqlite_error_converts_via_question_mark() {
fn do_thing() -> Result<()> {
let conn = rusqlite::Connection::open_in_memory()?;
conn.execute("this is not valid SQL", [])?;
Ok(())
}
let err = do_thing().unwrap_err();
assert!(matches!(err, LogdiveError::Sqlite(_)));
}
#[test]
fn json_error_converts_via_question_mark() {
fn do_thing() -> Result<serde_json::Value> {
let v = serde_json::from_str("not json")?;
Ok(v)
}
let err = do_thing().unwrap_err();
assert!(matches!(err, LogdiveError::Json(_)));
}
#[test]
fn io_at_attaches_path_to_error_message() {
let src = io::Error::new(io::ErrorKind::NotFound, "missing");
let err = LogdiveError::io_at("/tmp/never-exists.db", src);
let msg = format!("{err}");
assert!(msg.contains("/tmp/never-exists.db"));
assert!(msg.contains("missing"));
}
#[test]
fn invalid_datetime_formats_both_input_and_reason() {
let err = LogdiveError::InvalidDatetime {
input: "not-a-date".to_string(),
reason: "expected RFC3339".to_string(),
};
let msg = format!("{err}");
assert!(msg.contains("not-a-date"));
assert!(msg.contains("RFC3339"));
}
}