zenyx-engine-telemetry/engine/src/error.rs

259 lines
7.6 KiB
Rust

use std::fmt::Write;
use colored::Colorize;
use thiserror::Error;
#[derive(Debug, Error, PartialEq)]
pub enum ZenyxErrorKind {
#[error("Surface creation failed")]
SurfaceCreation,
#[error("Surface configuration failed")]
SurfaceConfiguration,
#[error("Adapter request failed")]
AdapterRequest,
#[error("Device request failed")]
DeviceRequest,
#[error("Surface texture acquisition failed")]
SurfaceTexture,
#[error("Command parsing failed")]
CommandParsing,
#[error("Command execution failed")]
CommandExecution,
#[error("Font loading failed")]
FontLoading,
#[error("Model loading failed")]
ModelLoading,
#[error("IO operation failed")]
Io,
#[error("REPL operation failed")]
Repl,
#[error("Unknown error occurred")]
Unknown,
}
#[derive(Debug)]
pub struct ZenyxError {
kind: ZenyxErrorKind,
message: Option<String>,
context: Option<String>,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
}
impl ZenyxError {
pub fn builder(kind: ZenyxErrorKind) -> Self {
Self {
kind,
message: None,
context: None,
source: None,
}
}
pub fn kind(&self) -> &ZenyxErrorKind {
&self.kind
}
pub fn with_message(mut self, message: impl Into<String>) -> Self {
self.message = Some(message.into());
self
}
pub fn with_context(mut self, context: impl Into<String>) -> Self {
self.context = Some(context.into());
self
}
pub fn with_source<E>(mut self, source: E) -> Self
where
E: std::error::Error + Send + Sync + 'static,
{
self.source = Some(Box::new(source));
self
}
pub fn build(self) -> Self {
self
}
pub fn pretty_print(&self) {
let mut output = String::new();
let padding_spaces = 2;
writeln!(
output,
"{} {}",
"\nERROR:".red().bold(),
format!("{}", self.kind).bright_white().bold()
)
.unwrap();
if let Some(msg) = &self.message {
let line_padding = " ".repeat(padding_spaces);
writeln!(output, "{}>> {}\x1b[0m", line_padding, msg.bright_white()).unwrap();
}
if let Some(ctx) = &self.context {
let line_padding = " ".repeat(padding_spaces);
writeln!(output, "{}│\x1b[0m", line_padding.bright_white().bold()).unwrap();
writeln!(output, "{}╰─ Note: {}\x1b[0m", line_padding, ctx).unwrap();
}
if let Some(source) = &self.source {
let line_padding = " ".repeat(padding_spaces);
writeln!(output, "{}╰─ Caused by: {}\x1b[0m", line_padding, source).unwrap();
let mut current = source.source();
let mut depth = 1;
while let Some(err) = current {
let indent = " ".repeat(padding_spaces * depth);
writeln!(output, "{}↳ {}\x1b[0m", indent, err).unwrap();
depth += 1;
current = err.source();
}
}
print!("{}", output);
}
}
impl std::fmt::Display for ZenyxError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}{}{}{}",
self.kind,
self.message
.as_ref()
.map_or("".to_string(), |msg| format!(" - {}", msg)),
self.context
.as_ref()
.map_or("".to_string(), |ctx| format!(" [{}]", ctx)),
self.source
.as_ref()
.map_or("".to_string(), |src| format!(" - caused by: {}", src))
)
}
}
impl std::error::Error for ZenyxError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source
.as_ref()
.map(|s| s.as_ref() as &(dyn std::error::Error + 'static))
}
}
impl From<std::io::Error> for ZenyxError {
fn from(err: std::io::Error) -> Self {
Self::builder(ZenyxErrorKind::Io)
.with_message(err.to_string())
.with_source(err)
.build()
}
}
impl From<wgpu::CreateSurfaceError> for ZenyxError {
fn from(err: wgpu::CreateSurfaceError) -> Self {
Self::builder(ZenyxErrorKind::SurfaceCreation)
.with_message("Failed to create surface")
.with_source(err)
.build()
}
}
impl From<wgpu::RequestDeviceError> for ZenyxError {
fn from(err: wgpu::RequestDeviceError) -> Self {
Self::builder(ZenyxErrorKind::DeviceRequest)
.with_message("Failed to request device")
.with_source(err)
.build()
}
}
impl From<rustyline::error::ReadlineError> for ZenyxError {
fn from(err: rustyline::error::ReadlineError) -> Self {
Self::builder(ZenyxErrorKind::Repl)
.with_message("Readline error occurred")
.with_source(err)
.build()
}
}
pub type Result<T> = std::result::Result<T, ZenyxError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_zenyx_error_builder() {
let error = ZenyxError::builder(ZenyxErrorKind::Io)
.with_message("An IO error occurred")
.with_context("Reading file")
.build();
assert_eq!(error.kind, ZenyxErrorKind::Io);
assert_eq!(error.message.as_deref(), Some("An IO error occurred"));
assert_eq!(error.context.as_deref(), Some("Reading file"));
assert!(error.source.is_none());
}
#[test]
fn test_zenyx_error_with_source() {
let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found");
let error = ZenyxError::builder(ZenyxErrorKind::Io)
.with_message("An IO error occurred")
.with_source(io_error)
.build();
assert_eq!(error.kind, ZenyxErrorKind::Io);
assert_eq!(error.message.as_deref(), Some("An IO error occurred"));
assert!(error.source.is_some());
}
#[test]
fn test_from_io_error() {
let io_error = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "Access denied");
let error: ZenyxError = io_error.into();
assert_eq!(error.kind, ZenyxErrorKind::Io);
assert_eq!(error.message.as_deref(), Some("Access denied"));
assert!(error.source.is_some());
}
#[test]
fn test_from_rustyline_error() {
let readline_error = rustyline::error::ReadlineError::Interrupted;
let error: ZenyxError = readline_error.into();
assert_eq!(error.kind, ZenyxErrorKind::Repl);
assert_eq!(error.message.as_deref(), Some("Readline error occurred"));
assert!(error.source.is_some());
}
#[test]
fn test_print() {
let readline_error = rustyline::error::ReadlineError::Interrupted;
let error: ZenyxError = readline_error.into();
println!("{error}");
}
#[test]
fn test_pretty_print() {
let readline_error = rustyline::error::ReadlineError::Interrupted;
let error: ZenyxError = readline_error.into();
error.pretty_print();
}
#[test]
fn test_error_source_chain() {
let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found");
let error = ZenyxError::builder(ZenyxErrorKind::Io)
.with_message("An IO error occurred")
.with_source(io_error)
.build();
let mut source = std::error::Error::source(&error);
assert!(source.is_some());
assert_eq!(source.unwrap().to_string(), "File not found");
source = source.unwrap().source();
assert!(source.is_none());
}
}