1use std::fmt;
2use std::path::{Path, PathBuf};
3
4#[derive(Debug, Clone, Default, PartialEq, Eq)]
6#[non_exhaustive]
7pub struct Report {
8 pub written: Vec<PathBuf>,
10 pub unchanged: Vec<PathBuf>,
12 pub refused: Vec<RefusedFile>,
14 pub stale: Vec<PathBuf>,
17 pub missing: Vec<PathBuf>,
19 pub changed: Vec<PathBuf>,
21 pub table_failures: Vec<TableFailure>,
23 pub query_failures: Vec<QueryFailure>,
25}
26
27impl Report {
28 pub fn has_drift(&self) -> bool {
30 !(self.missing.is_empty() && self.changed.is_empty() && self.stale.is_empty())
31 }
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
36#[non_exhaustive]
37pub struct RefusedFile {
38 pub path: PathBuf,
39 pub reason: String,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
44#[non_exhaustive]
45pub struct TableFailure {
46 pub table: String,
48 pub reason: String,
49 pub path: Option<PathBuf>,
51 pub line: Option<usize>,
53}
54
55impl TableFailure {
56 pub(crate) fn location(&self) -> String {
58 location(self.path.as_deref(), self.line)
59 }
60}
61
62impl fmt::Display for TableFailure {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 write!(
65 f,
66 "{}table `{}`: {}",
67 self.location(),
68 self.table,
69 self.reason
70 )
71 }
72}
73
74pub(crate) fn display_path(path: &Path) -> String {
78 let current = std::env::current_dir().ok();
79 let shown = current
80 .as_deref()
81 .and_then(|current| path.strip_prefix(current).ok())
82 .filter(|relative| !relative.as_os_str().is_empty())
83 .unwrap_or(path);
84 let mut text = shown.display().to_string();
85 if std::path::MAIN_SEPARATOR == '\\' {
86 text = text.replace('\\', "/");
87 }
88 while let Some(rest) = text.strip_prefix("./") {
89 text = rest.to_owned();
90 }
91 text
92}
93
94fn location(path: Option<&Path>, line: Option<usize>) -> String {
96 match (path, line) {
97 (Some(path), Some(line)) => format!("{}:{line}: ", display_path(path)),
98 _ => String::new(),
99 }
100}
101
102#[derive(Debug, Clone, PartialEq, Eq)]
104#[non_exhaustive]
105pub struct QueryFailure {
106 pub query: String,
108 pub reason: String,
109 pub path: Option<PathBuf>,
111 pub line: Option<usize>,
114}
115
116impl QueryFailure {
117 pub(crate) fn location(&self) -> String {
119 location(self.path.as_deref(), self.line)
120 }
121}
122
123impl fmt::Display for QueryFailure {
124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125 write!(
126 f,
127 "{}query `{}`: {}",
128 self.location(),
129 self.query,
130 self.reason
131 )
132 }
133}
134
135pub struct Error {
140 inner: anyhow::Error,
141 report: Box<Report>,
142}
143
144impl Error {
145 pub(crate) fn new(inner: anyhow::Error, report: Report) -> Self {
146 Self {
147 inner,
148 report: Box::new(report),
149 }
150 }
151
152 pub fn report(&self) -> &Report {
154 &self.report
155 }
156}
157
158impl fmt::Display for Error {
159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160 fmt::Display::fmt(&self.inner, f)
161 }
162}
163
164impl fmt::Debug for Error {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 fmt::Debug::fmt(&self.inner, f)
167 }
168}
169
170impl std::error::Error for Error {
171 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
172 self.inner.source()
173 }
174}
175
176pub type Result<T, E = Error> = std::result::Result<T, E>;