pub type BoxedError = Box<dyn std::error::Error + Send + Sync + 'static>;
#[derive(Debug, thiserror::Error)]
pub enum KernexError {
#[error(transparent)]
Provider(BoxedError),
#[error(transparent)]
Store(BoxedError),
#[error(transparent)]
Sandbox(BoxedError),
#[error("config error: {0}")]
Config(String),
#[error(transparent)]
Pipeline(BoxedError),
#[error(transparent)]
Skill(BoxedError),
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Serialization(#[from] serde_json::Error),
#[error("guardrail blocked: {0}")]
Guardrail(String),
}
impl KernexError {
pub fn provider<E: std::error::Error + Send + Sync + 'static>(e: E) -> Self {
KernexError::Provider(Box::new(e))
}
pub fn store<E: std::error::Error + Send + Sync + 'static>(e: E) -> Self {
KernexError::Store(Box::new(e))
}
pub fn sandbox<E: std::error::Error + Send + Sync + 'static>(e: E) -> Self {
KernexError::Sandbox(Box::new(e))
}
pub fn pipeline<E: std::error::Error + Send + Sync + 'static>(e: E) -> Self {
KernexError::Pipeline(Box::new(e))
}
pub fn skill<E: std::error::Error + Send + Sync + 'static>(e: E) -> Self {
KernexError::Skill(Box::new(e))
}
}
pub type Result<T> = std::result::Result<T, KernexError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn io_error_display_passes_through() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
let err = KernexError::from(io_err);
let display = format!("{err}");
assert!(display.contains("file missing"));
}
#[test]
fn config_error_display() {
let err = KernexError::Config("missing field".into());
assert_eq!(format!("{err}"), "config error: missing field");
}
#[test]
fn guardrail_error_display() {
let err = KernexError::Guardrail("blocked".into());
assert_eq!(format!("{err}"), "guardrail blocked: blocked");
}
#[derive(Debug, thiserror::Error)]
enum ToyError {
#[error("network: {0}")]
Network(String),
#[error("parse: {0}")]
Parse(String),
}
#[test]
fn provider_variant_round_trips_typed_error_via_downcast() {
let original = ToyError::Network("connection refused".into());
let err = KernexError::provider(original);
assert!(format!("{err}").contains("connection refused"));
match &err {
KernexError::Provider(boxed) => {
let typed = boxed
.downcast_ref::<ToyError>()
.expect("boxed value must be ToyError");
assert!(matches!(typed, ToyError::Network(_)));
}
other => panic!("expected Provider, got {other:?}"),
}
}
#[test]
fn store_variant_round_trips_typed_error_via_downcast() {
let original = ToyError::Parse("bad json".into());
let err = KernexError::store(original);
match &err {
KernexError::Store(boxed) => {
assert!(matches!(
boxed.downcast_ref::<ToyError>(),
Some(ToyError::Parse(_))
));
}
other => panic!("expected Store, got {other:?}"),
}
}
}