use thiserror::Error;
#[derive(Debug, Error)]
pub enum KernelError {
#[error("Agent {id} not found")]
AgentNotFound {
id: crate::types::AgentId,
},
#[error("Permission denied: {reason}")]
PermissionDenied {
reason: String,
},
#[error("Program '{name}' not found")]
ProgramNotFound {
name: String,
},
#[error("Program '{name}' already installed")]
ProgramAlreadyExists {
name: String,
},
#[error("Invalid configuration: {detail}")]
InvalidConfig {
detail: String,
},
#[error("Seed '{id}' not found")]
SeedNotFound {
id: String,
},
#[error("Session '{id}' not found")]
SessionNotFound {
id: String,
},
#[error("State store error: {0}")]
StateStore(#[from] std::io::Error),
#[error("{0}")]
Internal(#[from] anyhow::Error),
#[error("Memory error: {reason}")]
Memory {
reason: String,
},
#[error("Operation timed out: {context}")]
Timeout {
context: String,
},
#[error("Rate limit exceeded: {context}")]
RateLimited {
context: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HttpStatus {
Ok = 200,
BadRequest = 400,
Forbidden = 403,
NotFound = 404,
Conflict = 409,
TooManyRequests = 429,
InternalServerError = 500,
ServiceUnavailable = 503,
}
impl From<HttpStatus> for u16 {
fn from(status: HttpStatus) -> u16 {
status as u16
}
}
impl KernelError {
pub fn http_status(&self) -> HttpStatus {
match self {
Self::AgentNotFound { .. } => HttpStatus::NotFound,
Self::PermissionDenied { .. } => HttpStatus::Forbidden,
Self::ProgramNotFound { .. } => HttpStatus::NotFound,
Self::ProgramAlreadyExists { .. } => HttpStatus::Conflict,
Self::InvalidConfig { .. } => HttpStatus::BadRequest,
Self::SeedNotFound { .. } => HttpStatus::NotFound,
Self::SessionNotFound { .. } => HttpStatus::NotFound,
Self::StateStore(_) => HttpStatus::InternalServerError,
Self::Memory { .. } => HttpStatus::InternalServerError,
Self::Timeout { .. } => HttpStatus::ServiceUnavailable,
Self::RateLimited { .. } => HttpStatus::TooManyRequests,
Self::Internal(_) => HttpStatus::InternalServerError,
}
}
}
pub type KernelResult<T> = Result<T, KernelError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let id = crate::types::AgentId::new_v4();
let err = KernelError::AgentNotFound { id };
let msg = err.to_string();
assert!(msg.contains("not found"));
}
#[test]
fn test_all_http_status_mappings() {
let id = crate::types::AgentId::new_v4();
assert_eq!(
u16::from(KernelError::AgentNotFound { id }.http_status()),
404
);
assert_eq!(
u16::from(
KernelError::PermissionDenied {
reason: "test".into()
}
.http_status()
),
403
);
assert_eq!(
u16::from(KernelError::ProgramNotFound { name: "p".into() }.http_status()),
404
);
assert_eq!(
u16::from(KernelError::ProgramAlreadyExists { name: "p".into() }.http_status()),
409
);
assert_eq!(
u16::from(
KernelError::InvalidConfig {
detail: "bad".into()
}
.http_status()
),
400
);
assert_eq!(
u16::from(KernelError::SeedNotFound { id: "s".into() }.http_status()),
404
);
assert_eq!(
u16::from(KernelError::SessionNotFound { id: "s".into() }.http_status()),
404
);
}
#[test]
fn test_internal_error_wrapping() {
let err = KernelError::Internal(anyhow::anyhow!("something broke"));
assert!(err.to_string().contains("something broke"));
assert_eq!(u16::from(err.http_status()), 500);
}
#[test]
fn test_io_error_conversion() {
let err =
KernelError::StateStore(std::io::Error::new(std::io::ErrorKind::NotFound, "gone"));
assert!(err.to_string().contains("gone"));
assert_eq!(u16::from(err.http_status()), 500);
}
#[test]
fn test_timeout_error_status() {
let err = KernelError::Timeout {
context: "agent execution exceeded 300s".into(),
};
assert!(err.to_string().contains("timed out"));
assert!(err.to_string().contains("300s"));
assert_eq!(u16::from(err.http_status()), 503);
}
#[test]
fn test_rate_limited_error_status() {
let err = KernelError::RateLimited {
context: "API calls exceeded 60/min".into(),
};
assert!(err.to_string().contains("Rate limit exceeded"));
assert!(err.to_string().contains("60/min"));
assert_eq!(u16::from(err.http_status()), 429);
}
}