use miette::{Diagnostic, NamedSource, SourceOffset, SourceSpan};
use thiserror::Error;
#[derive(Error, Debug, Diagnostic)]
pub enum DieselGuardError {
#[error("Failed to parse SQL: {msg}")]
#[diagnostic(help("Check that your SQL syntax is valid"))]
ParseError {
msg: String,
#[source_code]
src: Option<NamedSource<String>>,
#[label("problematic SQL")]
span: Option<SourceSpan>,
},
#[error("Failed to read file")]
#[diagnostic(
code(diesel_guard::io_error),
help("Ensure the file exists and you have read permissions")
)]
IoError(#[from] std::io::Error),
#[error("Failed to traverse directory")]
#[diagnostic(
code(diesel_guard::walkdir_error),
help("Check directory permissions and path validity")
)]
WalkDirError(#[from] walkdir::Error),
#[error(transparent)]
#[diagnostic(transparent)]
ConfigError(#[from] crate::config::ConfigError),
}
impl DieselGuardError {
pub fn parse_error(msg: impl Into<String>) -> Self {
Self::ParseError {
msg: msg.into(),
src: None,
span: None,
}
}
#[must_use]
pub fn with_file_context(self, path: &str, source: String) -> Self {
match self {
Self::ParseError { msg, span, .. } => {
let span = span
.or_else(|| {
parse_byte_position(&msg).map(|p| SourceSpan::new(SourceOffset::from(p), 0))
})
.unwrap_or_else(|| SourceSpan::new(SourceOffset::from(0), 0));
Self::ParseError {
msg,
src: Some(NamedSource::new(path, source)),
span: Some(span),
}
}
other => other,
}
}
}
fn parse_byte_position(msg: &str) -> Option<usize> {
let pos_str = msg.rsplit_once("at position ")?.1;
let end = pos_str
.find(|c: char| !c.is_ascii_digit())
.unwrap_or(pos_str.len());
let pos: usize = pos_str[..end].parse().ok()?;
Some(pos.saturating_sub(1))
}
pub type Result<T> = std::result::Result<T, DieselGuardError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_byte_position() {
let msg = "syntax error at or near \"INVALID\" at position 42";
assert_eq!(parse_byte_position(msg), Some(41)); }
#[test]
fn test_parse_byte_position_no_position() {
let msg = "some error without position info";
assert_eq!(parse_byte_position(msg), None);
}
#[test]
fn test_parse_byte_position_single_digit() {
let msg = "error at position 1";
assert_eq!(parse_byte_position(msg), Some(0)); }
}