#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum Severity {
Debug,
#[default]
Info,
Warn,
Error,
Critical,
}
impl Severity {
pub fn to_tracing_level(self) -> tracing::Level {
match self {
Severity::Debug => tracing::Level::DEBUG,
Severity::Info => tracing::Level::INFO,
Severity::Warn => tracing::Level::WARN,
Severity::Error | Severity::Critical => tracing::Level::ERROR,
}
}
pub fn is_production_visible(self) -> bool {
self >= Severity::Info
}
}
impl std::fmt::Display for Severity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Severity::Debug => write!(f, "DEBUG"),
Severity::Info => write!(f, "INFO"),
Severity::Warn => write!(f, "WARN"),
Severity::Error => write!(f, "ERROR"),
Severity::Critical => write!(f, "CRITICAL"),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ErrorSource {
Command,
Runtime,
Subscription,
View,
}
impl std::fmt::Display for ErrorSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ErrorSource::Command => write!(f, "Command"),
ErrorSource::Runtime => write!(f, "Runtime"),
ErrorSource::Subscription => write!(f, "Subscription"),
ErrorSource::View => write!(f, "View"),
}
}
}
#[derive(Clone, Debug)]
pub struct FrameworkError {
pub severity: Severity,
pub source: ErrorSource,
pub message: String,
pub context: Option<String>,
}
impl FrameworkError {
pub fn new(severity: Severity, source: ErrorSource, message: impl Into<String>) -> Self {
Self {
severity,
source,
message: message.into(),
context: None,
}
}
pub fn with_context(mut self, ctx: impl Into<String>) -> Self {
self.context = Some(ctx.into());
self
}
pub fn command(severity: Severity, message: impl Into<String>) -> Self {
Self::new(severity, ErrorSource::Command, message)
}
pub fn runtime(severity: Severity, message: impl Into<String>) -> Self {
Self::new(severity, ErrorSource::Runtime, message)
}
pub fn subscription(severity: Severity, message: impl Into<String>) -> Self {
Self::new(severity, ErrorSource::Subscription, message)
}
pub fn view(severity: Severity, message: impl Into<String>) -> Self {
Self::new(severity, ErrorSource::View, message)
}
pub fn log(&self) {
let msg = self.format_message();
match self.severity {
Severity::Debug => tracing::debug!("{}", msg),
Severity::Info => tracing::info!("{}", msg),
Severity::Warn => tracing::warn!("{}", msg),
Severity::Error => tracing::error!("{}", msg),
Severity::Critical => tracing::error!("[CRITICAL] {}", msg),
}
}
pub fn format_message(&self) -> String {
match &self.context {
Some(ctx) => format!("[{}] {} ({})", self.source, self.message, ctx),
None => format!("[{}] {}", self.source, self.message),
}
}
}
impl std::fmt::Display for FrameworkError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.format_message())
}
}
impl std::error::Error for FrameworkError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn severity_ordering() {
assert!(Severity::Debug < Severity::Info);
assert!(Severity::Info < Severity::Warn);
assert!(Severity::Warn < Severity::Error);
assert!(Severity::Error < Severity::Critical);
}
#[test]
fn severity_production_visibility() {
assert!(!Severity::Debug.is_production_visible());
assert!(Severity::Info.is_production_visible());
assert!(Severity::Warn.is_production_visible());
assert!(Severity::Error.is_production_visible());
assert!(Severity::Critical.is_production_visible());
}
#[test]
fn framework_error_formatting() {
let err = FrameworkError::command(Severity::Error, "Task failed");
assert_eq!(err.format_message(), "[Command] Task failed");
let err_with_ctx = err.with_context("user_id=123");
assert_eq!(
err_with_ctx.format_message(),
"[Command] Task failed (user_id=123)"
);
}
}