use std::fmt;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct Report {
pub written: Vec<PathBuf>,
pub unchanged: Vec<PathBuf>,
pub refused: Vec<RefusedFile>,
pub stale: Vec<PathBuf>,
pub missing: Vec<PathBuf>,
pub changed: Vec<PathBuf>,
pub table_failures: Vec<TableFailure>,
pub query_failures: Vec<QueryFailure>,
}
impl Report {
pub fn has_drift(&self) -> bool {
!(self.missing.is_empty() && self.changed.is_empty() && self.stale.is_empty())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct RefusedFile {
pub path: PathBuf,
pub reason: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct TableFailure {
pub table: String,
pub reason: String,
pub path: Option<PathBuf>,
pub line: Option<usize>,
}
impl TableFailure {
pub(crate) fn location(&self) -> String {
location(self.path.as_deref(), self.line)
}
}
impl fmt::Display for TableFailure {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}table `{}`: {}",
self.location(),
self.table,
self.reason
)
}
}
pub(crate) fn display_path(path: &Path) -> String {
let current = std::env::current_dir().ok();
let shown = current
.as_deref()
.and_then(|current| path.strip_prefix(current).ok())
.filter(|relative| !relative.as_os_str().is_empty())
.unwrap_or(path);
let mut text = shown.display().to_string();
if std::path::MAIN_SEPARATOR == '\\' {
text = text.replace('\\', "/");
}
while let Some(rest) = text.strip_prefix("./") {
text = rest.to_owned();
}
text
}
fn location(path: Option<&Path>, line: Option<usize>) -> String {
match (path, line) {
(Some(path), Some(line)) => format!("{}:{line}: ", display_path(path)),
_ => String::new(),
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct QueryFailure {
pub query: String,
pub reason: String,
pub path: Option<PathBuf>,
pub line: Option<usize>,
}
impl QueryFailure {
pub(crate) fn location(&self) -> String {
location(self.path.as_deref(), self.line)
}
}
impl fmt::Display for QueryFailure {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}query `{}`: {}",
self.location(),
self.query,
self.reason
)
}
}
pub struct Error {
inner: anyhow::Error,
report: Box<Report>,
}
impl Error {
pub(crate) fn new(inner: anyhow::Error, report: Report) -> Self {
Self {
inner,
report: Box::new(report),
}
}
pub fn report(&self) -> &Report {
&self.report
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.inner, f)
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.inner, f)
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.inner.source()
}
}
pub type Result<T, E = Error> = std::result::Result<T, E>;