use std::path::PathBuf;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, UxieError>;
#[derive(Debug, Error)]
pub enum UxieError {
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("YAML error: {0}")]
Yaml(#[from] serde_yaml::Error),
#[error("Invalid format: {message}")]
InvalidFormat {
message: String,
},
#[error("{resource_type} not found: {id}")]
NotFound {
resource_type: &'static str,
id: String,
},
#[error("{resource_type} ID {id} out of range (max: {max})")]
OutOfBounds {
resource_type: &'static str,
id: u32,
max: u32,
},
#[error("Could not detect project type at {}", path.display())]
ProjectDetectionFailed {
path: PathBuf,
},
#[error("Could not detect game from ROM header")]
GameDetectionFailed,
#[error("Failed to resolve symbol: {name}")]
SymbolResolutionFailed {
name: String,
},
#[error("Missing required {item_type}: {}", path.display())]
MissingRequired {
item_type: &'static str,
path: PathBuf,
},
}
impl UxieError {
pub fn invalid_format(message: impl Into<String>) -> Self {
Self::InvalidFormat {
message: message.into(),
}
}
pub fn not_found(resource_type: &'static str, id: impl Into<String>) -> Self {
Self::NotFound {
resource_type,
id: id.into(),
}
}
pub fn out_of_bounds(resource_type: &'static str, id: u32, max: u32) -> Self {
Self::OutOfBounds {
resource_type,
id,
max,
}
}
pub fn missing_file(path: impl Into<PathBuf>) -> Self {
Self::MissingRequired {
item_type: "file",
path: path.into(),
}
}
pub fn missing_directory(path: impl Into<PathBuf>) -> Self {
Self::MissingRequired {
item_type: "directory",
path: path.into(),
}
}
}
impl From<UxieError> for std::io::Error {
fn from(err: UxieError) -> Self {
match err {
UxieError::Io(e) => e,
UxieError::Json(e) => std::io::Error::new(std::io::ErrorKind::InvalidData, e),
UxieError::Yaml(e) => std::io::Error::new(std::io::ErrorKind::InvalidData, e),
UxieError::InvalidFormat { message } => {
std::io::Error::new(std::io::ErrorKind::InvalidData, message)
}
UxieError::NotFound { resource_type, id } => std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("{} not found: {}", resource_type, id),
),
UxieError::OutOfBounds {
resource_type,
id,
max,
} => std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("{} ID {} out of range (max: {})", resource_type, id, max),
),
UxieError::ProjectDetectionFailed { path } => std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("Could not detect project type at {}", path.display()),
),
UxieError::GameDetectionFailed => std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Could not detect game from ROM header",
),
UxieError::SymbolResolutionFailed { name } => std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("Failed to resolve symbol: {}", name),
),
UxieError::MissingRequired { item_type, path } => std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("Missing required {}: {}", item_type, path.display()),
),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_io_error_conversion() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let uxie_err: UxieError = io_err.into();
assert!(matches!(uxie_err, UxieError::Io(_)));
assert!(uxie_err.to_string().contains("file not found"));
}
#[test]
fn test_invalid_format() {
let err = UxieError::invalid_format("Not a NARC file");
assert!(matches!(err, UxieError::InvalidFormat { .. }));
assert_eq!(err.to_string(), "Invalid format: Not a NARC file");
}
#[test]
fn test_not_found() {
let err = UxieError::not_found("Map header", "42");
assert!(matches!(err, UxieError::NotFound { .. }));
assert_eq!(err.to_string(), "Map header not found: 42");
}
#[test]
fn test_out_of_bounds() {
let err = UxieError::out_of_bounds("Map header", 600, 558);
assert!(matches!(err, UxieError::OutOfBounds { .. }));
assert_eq!(err.to_string(), "Map header ID 600 out of range (max: 558)");
}
#[test]
fn test_missing_file() {
let err = UxieError::missing_file("/path/to/arm9.bin");
assert!(matches!(err, UxieError::MissingRequired { .. }));
assert!(err.to_string().contains("arm9.bin"));
}
#[test]
fn test_backward_compat_io_conversion() {
let uxie_err = UxieError::not_found("Script", "main");
let io_err: std::io::Error = uxie_err.into();
assert_eq!(io_err.kind(), std::io::ErrorKind::NotFound);
}
}