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 Unsupported,
90 InstrumentFailure,
92 Internal,
94 Unknown,
96}
97
98impl CargoAllowErrorKind {
99 pub const ALL: &[Self] = &[
103 Self::Usage,
104 Self::InvalidConfig,
105 Self::InvalidPolicy,
106 Self::Inventory,
107 Self::Scan,
108 Self::PolicyViolation,
109 Self::Artifact,
110 Self::Unsupported,
111 Self::InstrumentFailure,
112 Self::Internal,
113 Self::Unknown,
114 ];
115
116 pub fn as_str(self) -> &'static str {
119 match self {
120 Self::Usage => "usage",
121 Self::InvalidConfig => "invalid_config",
122 Self::InvalidPolicy => "invalid_policy",
123 Self::Inventory => "inventory",
124 Self::Scan => "scan",
125 Self::PolicyViolation => "policy_violation",
126 Self::Artifact => "artifact",
127 Self::Unsupported => "unsupported",
128 Self::InstrumentFailure => "instrument_failure",
129 Self::Internal => "internal",
130 Self::Unknown => "unknown",
131 }
132 }
133
134 pub const fn code(self) -> &'static str {
139 match self {
140 Self::Usage => "E0001_USAGE",
141 Self::InvalidConfig => "E0002_INVALID_CONFIG",
142 Self::InvalidPolicy => "E0003_INVALID_POLICY",
143 Self::Inventory => "E0004_INVENTORY",
144 Self::Scan => "E0005_SCAN",
145 Self::PolicyViolation => "E0006_POLICY_VIOLATION",
146 Self::Artifact => "E0007_ARTIFACT",
147 Self::Internal => "E0008_INTERNAL",
148 Self::Unknown => "E0009_UNKNOWN",
149 Self::Unsupported => "E0010_UNSUPPORTED",
150 Self::InstrumentFailure => "E0011_INSTRUMENT_FAILURE",
151 }
152 }
153}
154
155impl fmt::Display for CargoAllowErrorKind {
156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157 f.write_str(self.as_str())
158 }
159}
160
161#[derive(Debug, Clone)]
163struct CauseError {
164 message: String,
165 next: Option<Box<CauseError>>,
166}
167
168impl fmt::Display for CauseError {
169 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170 f.write_str(&self.message)
171 }
172}
173
174impl std::error::Error for CauseError {
175 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
176 self.next
177 .as_ref()
178 .map(|next| next.as_ref() as &(dyn std::error::Error + 'static))
179 }
180}
181
182#[derive(Debug, Clone)]
189pub struct CargoAllowError {
190 kind: CargoAllowErrorKind,
191 message: String,
192 location: Option<CargoAllowErrorLocation>,
193 diagnostics: Vec<CargoAllowDiagnostic>,
194 causes: Vec<String>,
197 source: Option<Box<CauseError>>,
199}
200
201impl CargoAllowError {
202 pub fn new(message: impl Into<String>) -> Self {
204 Self {
205 kind: CargoAllowErrorKind::Unknown,
206 message: message.into(),
207 location: None,
208 diagnostics: Vec::new(),
209 causes: Vec::new(),
210 source: None,
211 }
212 }
213
214 pub fn with_kind(kind: CargoAllowErrorKind, message: impl Into<String>) -> Self {
216 Self {
217 kind,
218 message: message.into(),
219 location: None,
220 diagnostics: Vec::new(),
221 causes: Vec::new(),
222 source: None,
223 }
224 }
225
226 pub fn with_kind_preserving_metadata(mut self, kind: CargoAllowErrorKind) -> Self {
232 self.kind = kind;
233 self
234 }
235
236 pub fn with_cause(mut self, cause: &(impl std::error::Error + ?Sized)) -> Self {
241 let message = cause.to_string();
242 self.causes.push(message.clone());
243 let node = Box::new(CauseError {
244 message,
245 next: None,
246 });
247 match self.source.as_mut() {
248 None => self.source = Some(node),
249 Some(head) => append_cause(head, node),
250 }
251 self
252 }
253
254 pub fn with_message_prefix(mut self, prefix: impl AsRef<str>) -> Self {
261 let prefix = prefix.as_ref();
262 if !prefix.is_empty() {
263 self.message.insert_str(0, prefix);
264 }
265 self
266 }
267
268 pub fn with_message_suffix(mut self, suffix: impl AsRef<str>) -> Self {
274 let suffix = suffix.as_ref();
275 if !suffix.is_empty() {
276 self.message.push_str(suffix);
277 }
278 self
279 }
280
281 pub fn causes(&self) -> &[String] {
283 &self.causes
284 }
285
286 pub fn with_diagnostic(mut self, diagnostic: CargoAllowDiagnostic) -> Self {
288 self.diagnostics.push(diagnostic);
289 self
290 }
291
292 pub fn with_diagnostics(
294 mut self,
295 diagnostics: impl IntoIterator<Item = CargoAllowDiagnostic>,
296 ) -> Self {
297 self.diagnostics.extend(diagnostics);
298 self
299 }
300
301 pub fn diagnostics(&self) -> &[CargoAllowDiagnostic] {
303 &self.diagnostics
304 }
305
306 pub fn kind(&self) -> CargoAllowErrorKind {
308 self.kind
309 }
310
311 pub fn code(&self) -> &'static str {
313 self.kind.code()
314 }
315
316 pub fn with_toml_span(
322 mut self,
323 path: Option<&Path>,
324 source: &str,
325 span: Option<Range<usize>>,
326 ) -> Self {
327 let Some(span) = span else {
328 return self;
329 };
330 let prefix = source.get(..span.start).unwrap_or(source);
331 let line = prefix.bytes().filter(|byte| *byte == b'\n').count() + 1;
332 let column = prefix
333 .rsplit_once('\n')
334 .map(|(_, line)| line.chars().count() + 1)
335 .unwrap_or_else(|| prefix.chars().count() + 1);
336 self.location = Some(CargoAllowErrorLocation {
337 path: path.map(|value| value.display().to_string()),
338 line: u32::try_from(line).unwrap_or(u32::MAX),
339 column: u32::try_from(column).unwrap_or(u32::MAX),
340 });
341 for diagnostic in &mut self.diagnostics {
342 diagnostic.path = path.map(|value| value.display().to_string());
343 diagnostic.span = self.location.clone();
344 }
345 self
346 }
347
348 pub fn location(&self) -> Option<&CargoAllowErrorLocation> {
351 self.location.as_ref()
352 }
353
354 pub fn message(&self) -> &str {
356 &self.message
357 }
358}
359
360impl fmt::Display for CargoAllowError {
361 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
362 write!(f, "{}", self.message)?;
363 for cause in &self.causes {
364 write!(f, "\n caused by: {cause}")?;
365 }
366 Ok(())
367 }
368}
369
370impl std::error::Error for CargoAllowError {
371 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
372 self.source
373 .as_ref()
374 .map(|cause| cause.as_ref() as &(dyn std::error::Error + 'static))
375 }
376}
377
378impl From<std::io::Error> for CargoAllowError {
382 fn from(e: std::io::Error) -> Self {
383 let message = e.to_string();
384 let mut err = CargoAllowError::with_kind(CargoAllowErrorKind::Unknown, message.clone());
385 err.kind = match e.kind() {
386 std::io::ErrorKind::NotFound => CargoAllowErrorKind::InvalidConfig,
387 std::io::ErrorKind::PermissionDenied => CargoAllowErrorKind::Inventory,
388 _ => CargoAllowErrorKind::Unknown,
389 };
390 err.message = message.clone();
391 err.source = Some(Box::new(CauseError {
394 message,
395 next: None,
396 }));
397 err
398 }
399}
400
401fn append_cause(head: &mut CauseError, node: Box<CauseError>) {
402 match head.next.as_mut() {
403 Some(next) => append_cause(next, node),
404 None => head.next = Some(node),
405 }
406}
407
408pub type CargoAllowResult<T> = Result<T, CargoAllowError>;
409
410#[cfg(test)]
411#[path = "error_tests.rs"]
412mod tests;