use std::fmt;
use crate::parser::ParseError;
use crate::validator::ValidationIssue;
#[derive(Debug)]
#[non_exhaustive]
pub enum KernelError {
Parse {
errors: Vec<ParseError>,
message: String,
},
Validation {
issues: Vec<ValidationIssue>,
message: String,
},
Execution(anyhow::Error),
}
impl fmt::Display for KernelError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
KernelError::Parse { message, .. } | KernelError::Validation { message, .. } => {
f.write_str(message)
}
KernelError::Execution(e) => {
if f.alternate() {
write!(f, "{e:#}")
} else {
write!(f, "{e}")
}
}
}
}
}
impl std::error::Error for KernelError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
KernelError::Execution(e) => e.source(),
_ => None,
}
}
}
impl KernelError {
pub fn is_rejected(&self) -> bool {
matches!(self, KernelError::Parse { .. } | KernelError::Validation { .. })
}
pub fn is_execution_failure(&self) -> bool {
matches!(self, KernelError::Execution(_))
}
}
pub(crate) fn classify_execute_error(e: anyhow::Error) -> KernelError {
match e.downcast::<KernelError>() {
Ok(tagged) => tagged,
Err(e) => KernelError::Execution(e),
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn is_rejected_true_for_parse_and_validation() {
let parse = KernelError::Parse { errors: Vec::new(), message: "parse error:\nx".into() };
assert!(parse.is_rejected());
assert!(!parse.is_execution_failure());
let validation = KernelError::Validation { issues: Vec::new(), message: "validation failed:\nx".into() };
assert!(validation.is_rejected());
assert!(!validation.is_execution_failure());
}
#[test]
fn is_rejected_false_for_execution() {
let exec = KernelError::Execution(anyhow::anyhow!("boom"));
assert!(!exec.is_rejected());
assert!(exec.is_execution_failure());
}
#[test]
fn classify_recovers_a_tagged_rejection() {
let tagged = KernelError::Validation { issues: Vec::new(), message: "validation failed:\nx".into() };
let boxed = anyhow::Error::from(tagged);
let classified = classify_execute_error(boxed);
assert!(classified.is_rejected());
}
#[test]
fn classify_falls_back_to_execution_for_untagged_errors() {
let classified = classify_execute_error(anyhow::anyhow!("some deep interpreter error"));
assert!(matches!(classified, KernelError::Execution(_)));
}
#[test]
fn anyhow_error_from_kernel_error_works() {
fn as_anyhow() -> anyhow::Result<()> {
fn fails() -> Result<(), KernelError> {
Err(KernelError::Execution(anyhow::anyhow!("boom")))
}
fails()?;
Ok(())
}
assert!(as_anyhow().is_err());
}
}