use std::fmt;
use std::ops::Range;
use std::path::Path;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CargoAllowErrorLocation {
pub path: Option<String>,
pub line: u32,
pub column: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CargoAllowDiagnosticSeverity {
Error,
Warning,
Info,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CargoAllowDiagnostic {
pub code: String,
pub category: String,
pub severity: CargoAllowDiagnosticSeverity,
pub path: Option<String>,
pub span: Option<CargoAllowErrorLocation>,
pub entry_id: Option<String>,
pub field: Option<String>,
pub message: String,
pub help: Option<String>,
pub causes: Vec<String>,
}
impl CargoAllowDiagnostic {
pub fn error(
code: impl Into<String>,
category: impl Into<String>,
entry_id: Option<&str>,
field: Option<&str>,
message: impl Into<String>,
) -> Self {
Self {
code: code.into(),
category: category.into(),
severity: CargoAllowDiagnosticSeverity::Error,
path: None,
span: None,
entry_id: entry_id.map(str::to_owned),
field: field.map(str::to_owned),
message: message.into(),
help: None,
causes: Vec::new(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum CargoAllowErrorKind {
Usage,
InvalidConfig,
InvalidPolicy,
Inventory,
Scan,
PolicyViolation,
Artifact,
Internal,
Unknown,
}
impl CargoAllowErrorKind {
pub const ALL: &[Self] = &[
Self::Usage,
Self::InvalidConfig,
Self::InvalidPolicy,
Self::Inventory,
Self::Scan,
Self::PolicyViolation,
Self::Artifact,
Self::Internal,
Self::Unknown,
];
pub fn as_str(self) -> &'static str {
match self {
Self::Usage => "usage",
Self::InvalidConfig => "invalid_config",
Self::InvalidPolicy => "invalid_policy",
Self::Inventory => "inventory",
Self::Scan => "scan",
Self::PolicyViolation => "policy_violation",
Self::Artifact => "artifact",
Self::Internal => "internal",
Self::Unknown => "unknown",
}
}
pub const fn code(self) -> &'static str {
match self {
Self::Usage => "E0001_USAGE",
Self::InvalidConfig => "E0002_INVALID_CONFIG",
Self::InvalidPolicy => "E0003_INVALID_POLICY",
Self::Inventory => "E0004_INVENTORY",
Self::Scan => "E0005_SCAN",
Self::PolicyViolation => "E0006_POLICY_VIOLATION",
Self::Artifact => "E0007_ARTIFACT",
Self::Internal => "E0008_INTERNAL",
Self::Unknown => "E0009_UNKNOWN",
}
}
}
impl fmt::Display for CargoAllowErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone)]
struct CauseError {
message: String,
next: Option<Box<CauseError>>,
}
impl fmt::Display for CauseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.message)
}
}
impl std::error::Error for CauseError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.next
.as_ref()
.map(|next| next.as_ref() as &(dyn std::error::Error + 'static))
}
}
#[derive(Debug, Clone)]
pub struct CargoAllowError {
kind: CargoAllowErrorKind,
message: String,
location: Option<CargoAllowErrorLocation>,
diagnostics: Vec<CargoAllowDiagnostic>,
causes: Vec<String>,
source: Option<Box<CauseError>>,
}
impl CargoAllowError {
pub fn new(message: impl Into<String>) -> Self {
Self {
kind: CargoAllowErrorKind::Unknown,
message: message.into(),
location: None,
diagnostics: Vec::new(),
causes: Vec::new(),
source: None,
}
}
pub fn with_kind(kind: CargoAllowErrorKind, message: impl Into<String>) -> Self {
Self {
kind,
message: message.into(),
location: None,
diagnostics: Vec::new(),
causes: Vec::new(),
source: None,
}
}
pub fn with_cause(mut self, cause: &(impl std::error::Error + ?Sized)) -> Self {
let message = cause.to_string();
self.causes.push(message.clone());
let node = Box::new(CauseError {
message,
next: None,
});
match self.source.as_mut() {
None => self.source = Some(node),
Some(head) => append_cause(head, node),
}
self
}
pub fn causes(&self) -> &[String] {
&self.causes
}
pub fn with_diagnostic(mut self, diagnostic: CargoAllowDiagnostic) -> Self {
self.diagnostics.push(diagnostic);
self
}
pub fn with_diagnostics(
mut self,
diagnostics: impl IntoIterator<Item = CargoAllowDiagnostic>,
) -> Self {
self.diagnostics.extend(diagnostics);
self
}
pub fn diagnostics(&self) -> &[CargoAllowDiagnostic] {
&self.diagnostics
}
pub fn kind(&self) -> CargoAllowErrorKind {
self.kind
}
pub fn code(&self) -> &'static str {
self.kind.code()
}
pub fn with_toml_span(
mut self,
path: Option<&Path>,
source: &str,
span: Option<Range<usize>>,
) -> Self {
let Some(span) = span else {
return self;
};
let prefix = source.get(..span.start).unwrap_or(source);
let line = prefix.bytes().filter(|byte| *byte == b'\n').count() + 1;
let column = prefix
.rsplit_once('\n')
.map(|(_, line)| line.chars().count() + 1)
.unwrap_or_else(|| prefix.chars().count() + 1);
self.location = Some(CargoAllowErrorLocation {
path: path.map(|value| value.display().to_string()),
line: u32::try_from(line).unwrap_or(u32::MAX),
column: u32::try_from(column).unwrap_or(u32::MAX),
});
for diagnostic in &mut self.diagnostics {
diagnostic.path = path.map(|value| value.display().to_string());
diagnostic.span = self.location.clone();
}
self
}
pub fn location(&self) -> Option<&CargoAllowErrorLocation> {
self.location.as_ref()
}
pub fn message(&self) -> &str {
&self.message
}
}
impl fmt::Display for CargoAllowError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)?;
for cause in &self.causes {
write!(f, "\n caused by: {cause}")?;
}
Ok(())
}
}
impl std::error::Error for CargoAllowError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source
.as_ref()
.map(|cause| cause.as_ref() as &(dyn std::error::Error + 'static))
}
}
impl PartialEq for CargoAllowError {
fn eq(&self, other: &Self) -> bool {
self.kind == other.kind && self.message == other.message
}
}
impl Eq for CargoAllowError {}
impl From<std::io::Error> for CargoAllowError {
fn from(e: std::io::Error) -> Self {
let message = e.to_string();
let mut err = CargoAllowError::with_kind(CargoAllowErrorKind::Unknown, message.clone());
err.kind = match e.kind() {
std::io::ErrorKind::NotFound => CargoAllowErrorKind::InvalidConfig,
std::io::ErrorKind::PermissionDenied => CargoAllowErrorKind::Inventory,
_ => CargoAllowErrorKind::Unknown,
};
err.message = message.clone();
err.source = Some(Box::new(CauseError {
message,
next: None,
}));
err
}
}
fn append_cause(head: &mut CauseError, node: Box<CauseError>) {
match head.next.as_mut() {
Some(next) => append_cause(next, node),
None => head.next = Some(node),
}
}
pub type CargoAllowResult<T> = Result<T, CargoAllowError>;
#[cfg(test)]
#[path = "error_tests.rs"]
mod tests;