1use std::fmt;
2use std::ops::Range;
3use std::path::Path;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct CargoAllowErrorLocation {
8 pub path: Option<String>,
11 pub line: u32,
12 pub column: u32,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum CargoAllowDiagnosticSeverity {
18 Error,
19 Warning,
20 Info,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct CargoAllowDiagnostic {
30 pub code: String,
31 pub category: String,
32 pub severity: CargoAllowDiagnosticSeverity,
33 pub path: Option<String>,
34 pub span: Option<CargoAllowErrorLocation>,
35 pub entry_id: Option<String>,
36 pub field: Option<String>,
37 pub message: String,
38 pub help: Option<String>,
39 pub causes: Vec<String>,
40}
41
42impl CargoAllowDiagnostic {
43 pub fn error(
44 code: impl Into<String>,
45 category: impl Into<String>,
46 entry_id: Option<&str>,
47 field: Option<&str>,
48 message: impl Into<String>,
49 ) -> Self {
50 Self {
51 code: code.into(),
52 category: category.into(),
53 severity: CargoAllowDiagnosticSeverity::Error,
54 path: None,
55 span: None,
56 entry_id: entry_id.map(str::to_owned),
57 field: field.map(str::to_owned),
58 message: message.into(),
59 help: None,
60 causes: Vec::new(),
61 }
62 }
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
72#[non_exhaustive]
73pub enum CargoAllowErrorKind {
74 Usage,
76 InvalidConfig,
78 InvalidPolicy,
80 Inventory,
82 Scan,
84 PolicyViolation,
86 Artifact,
88 Internal,
90 Unknown,
92}
93
94impl CargoAllowErrorKind {
95 pub const ALL: &[Self] = &[
99 Self::Usage,
100 Self::InvalidConfig,
101 Self::InvalidPolicy,
102 Self::Inventory,
103 Self::Scan,
104 Self::PolicyViolation,
105 Self::Artifact,
106 Self::Internal,
107 Self::Unknown,
108 ];
109
110 pub fn as_str(self) -> &'static str {
113 match self {
114 Self::Usage => "usage",
115 Self::InvalidConfig => "invalid_config",
116 Self::InvalidPolicy => "invalid_policy",
117 Self::Inventory => "inventory",
118 Self::Scan => "scan",
119 Self::PolicyViolation => "policy_violation",
120 Self::Artifact => "artifact",
121 Self::Internal => "internal",
122 Self::Unknown => "unknown",
123 }
124 }
125
126 pub const fn code(self) -> &'static str {
131 match self {
132 Self::Usage => "E0001_USAGE",
133 Self::InvalidConfig => "E0002_INVALID_CONFIG",
134 Self::InvalidPolicy => "E0003_INVALID_POLICY",
135 Self::Inventory => "E0004_INVENTORY",
136 Self::Scan => "E0005_SCAN",
137 Self::PolicyViolation => "E0006_POLICY_VIOLATION",
138 Self::Artifact => "E0007_ARTIFACT",
139 Self::Internal => "E0008_INTERNAL",
140 Self::Unknown => "E0009_UNKNOWN",
141 }
142 }
143}
144
145impl fmt::Display for CargoAllowErrorKind {
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 f.write_str(self.as_str())
148 }
149}
150
151#[derive(Debug, Clone)]
153struct CauseError {
154 message: String,
155 next: Option<Box<CauseError>>,
156}
157
158impl fmt::Display for CauseError {
159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160 f.write_str(&self.message)
161 }
162}
163
164impl std::error::Error for CauseError {
165 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
166 self.next
167 .as_ref()
168 .map(|next| next.as_ref() as &(dyn std::error::Error + 'static))
169 }
170}
171
172#[derive(Debug, Clone)]
179pub struct CargoAllowError {
180 kind: CargoAllowErrorKind,
181 message: String,
182 location: Option<CargoAllowErrorLocation>,
183 diagnostics: Vec<CargoAllowDiagnostic>,
184 causes: Vec<String>,
187 source: Option<Box<CauseError>>,
189}
190
191impl CargoAllowError {
192 pub fn new(message: impl Into<String>) -> Self {
194 Self {
195 kind: CargoAllowErrorKind::Unknown,
196 message: message.into(),
197 location: None,
198 diagnostics: Vec::new(),
199 causes: Vec::new(),
200 source: None,
201 }
202 }
203
204 pub fn with_kind(kind: CargoAllowErrorKind, message: impl Into<String>) -> Self {
206 Self {
207 kind,
208 message: message.into(),
209 location: None,
210 diagnostics: Vec::new(),
211 causes: Vec::new(),
212 source: None,
213 }
214 }
215
216 pub fn with_cause(mut self, cause: &(impl std::error::Error + ?Sized)) -> Self {
221 let message = cause.to_string();
222 self.causes.push(message.clone());
223 let node = Box::new(CauseError {
224 message,
225 next: None,
226 });
227 match self.source.as_mut() {
228 None => self.source = Some(node),
229 Some(head) => append_cause(head, node),
230 }
231 self
232 }
233
234 pub fn causes(&self) -> &[String] {
236 &self.causes
237 }
238
239 pub fn with_diagnostic(mut self, diagnostic: CargoAllowDiagnostic) -> Self {
241 self.diagnostics.push(diagnostic);
242 self
243 }
244
245 pub fn with_diagnostics(
247 mut self,
248 diagnostics: impl IntoIterator<Item = CargoAllowDiagnostic>,
249 ) -> Self {
250 self.diagnostics.extend(diagnostics);
251 self
252 }
253
254 pub fn diagnostics(&self) -> &[CargoAllowDiagnostic] {
256 &self.diagnostics
257 }
258
259 pub fn kind(&self) -> CargoAllowErrorKind {
261 self.kind
262 }
263
264 pub fn code(&self) -> &'static str {
266 self.kind.code()
267 }
268
269 pub fn with_toml_span(
275 mut self,
276 path: Option<&Path>,
277 source: &str,
278 span: Option<Range<usize>>,
279 ) -> Self {
280 let Some(span) = span else {
281 return self;
282 };
283 let prefix = source.get(..span.start).unwrap_or(source);
284 let line = prefix.bytes().filter(|byte| *byte == b'\n').count() + 1;
285 let column = prefix
286 .rsplit_once('\n')
287 .map(|(_, line)| line.chars().count() + 1)
288 .unwrap_or_else(|| prefix.chars().count() + 1);
289 self.location = Some(CargoAllowErrorLocation {
290 path: path.map(|value| value.display().to_string()),
291 line: u32::try_from(line).unwrap_or(u32::MAX),
292 column: u32::try_from(column).unwrap_or(u32::MAX),
293 });
294 for diagnostic in &mut self.diagnostics {
295 diagnostic.path = path.map(|value| value.display().to_string());
296 diagnostic.span = self.location.clone();
297 }
298 self
299 }
300
301 pub fn location(&self) -> Option<&CargoAllowErrorLocation> {
304 self.location.as_ref()
305 }
306
307 pub fn message(&self) -> &str {
309 &self.message
310 }
311}
312
313impl fmt::Display for CargoAllowError {
314 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315 write!(f, "{}", self.message)?;
316 for cause in &self.causes {
317 write!(f, "\n caused by: {cause}")?;
318 }
319 Ok(())
320 }
321}
322
323impl std::error::Error for CargoAllowError {
324 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
325 self.source
326 .as_ref()
327 .map(|cause| cause.as_ref() as &(dyn std::error::Error + 'static))
328 }
329}
330
331impl PartialEq for CargoAllowError {
334 fn eq(&self, other: &Self) -> bool {
335 self.kind == other.kind && self.message == other.message
336 }
337}
338
339impl Eq for CargoAllowError {}
340
341impl From<std::io::Error> for CargoAllowError {
345 fn from(e: std::io::Error) -> Self {
346 let message = e.to_string();
347 let mut err = CargoAllowError::with_kind(CargoAllowErrorKind::Unknown, message.clone());
348 err.kind = match e.kind() {
349 std::io::ErrorKind::NotFound => CargoAllowErrorKind::InvalidConfig,
350 std::io::ErrorKind::PermissionDenied => CargoAllowErrorKind::Inventory,
351 _ => CargoAllowErrorKind::Unknown,
352 };
353 err.message = message.clone();
354 err.source = Some(Box::new(CauseError {
357 message,
358 next: None,
359 }));
360 err
361 }
362}
363
364fn append_cause(head: &mut CauseError, node: Box<CauseError>) {
365 match head.next.as_mut() {
366 Some(next) => append_cause(next, node),
367 None => head.next = Some(node),
368 }
369}
370
371pub type CargoAllowResult<T> = Result<T, CargoAllowError>;
372
373#[cfg(test)]
374#[path = "error_tests.rs"]
375mod tests;