forked from nonsensical-dev/zenyx-engine
259 lines
7.6 KiB
Rust
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());
|
|
}
|
|
}
|