use thiserror::Error;
#[derive(Debug, Error)]
pub enum AddonLoaderError {
#[error("addon '{0}' not found")]
AddonNotFound(String),
#[error("addon '{0}' is disabled")]
AddonDisabled(String),
#[error("addon controller '{0}' not found")]
ControllerNotFound(String),
#[error("addon action '{0}' not found")]
ActionNotFound(String),
#[error("failed to parse manifest for addon '{addon}': {reason}")]
ManifestParse {
addon: String,
reason: String,
},
#[error("failed to scan addons directory '{path}': {source}")]
ScanDir {
path: String,
source: std::io::Error,
},
#[error("failed to read file '{path}': {source}")]
ReadFile {
path: String,
source: std::io::Error,
},
#[error("autoload failed: class '{class}' not mapped to any file")]
AutoloadMiss {
class: String,
},
#[error("failed to register hook '{hook}' for addon '{addon}'")]
HookRegister {
hook: String,
addon: String,
},
#[error("failed to parse route '{url}': {reason}")]
RouteParse {
url: String,
reason: String,
},
}
impl From<std::io::Error> for AddonLoaderError {
fn from(err: std::io::Error) -> Self {
AddonLoaderError::ReadFile {
path: "<unknown>".to_string(),
source: err,
}
}
}
pub type AddonLoaderResult<T> = Result<T, AddonLoaderError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_addon_not_found_display() {
let err = AddonLoaderError::AddonNotFound("operate".to_string());
assert_eq!(err.to_string(), "addon 'operate' not found");
}
#[test]
fn test_addon_disabled_display() {
let err = AddonLoaderError::AddonDisabled("test".to_string());
assert_eq!(err.to_string(), "addon 'test' is disabled");
}
#[test]
fn test_controller_not_found_display() {
let err = AddonLoaderError::ControllerNotFound("admin.Order".to_string());
assert_eq!(err.to_string(), "addon controller 'admin.Order' not found");
}
#[test]
fn test_action_not_found_display() {
let err = AddonLoaderError::ActionNotFound("index".to_string());
assert_eq!(err.to_string(), "addon action 'index' not found");
}
#[test]
fn test_manifest_parse_display() {
let err = AddonLoaderError::ManifestParse {
addon: "operate".to_string(),
reason: "missing $info array".to_string(),
};
assert!(err.to_string().contains("operate"));
assert!(err.to_string().contains("missing $info array"));
}
#[test]
fn test_scan_dir_display() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "no such directory");
let err = AddonLoaderError::ScanDir {
path: "/addons".to_string(),
source: io_err,
};
assert!(err.to_string().contains("/addons"));
}
#[test]
fn test_read_file_display() {
let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "denied");
let err = AddonLoaderError::ReadFile {
path: "/addons/operate/Plugin.php".to_string(),
source: io_err,
};
assert!(err.to_string().contains("Plugin.php"));
}
#[test]
fn test_autoload_miss_display() {
let err = AddonLoaderError::AutoloadMiss {
class: "addons\\operate\\Plugin".to_string(),
};
assert!(err.to_string().contains("addons\\operate\\Plugin"));
}
#[test]
fn test_hook_register_display() {
let err = AddonLoaderError::HookRegister {
hook: "AddonsInit".to_string(),
addon: "operate".to_string(),
};
assert!(err.to_string().contains("AddonsInit"));
assert!(err.to_string().contains("operate"));
}
#[test]
fn test_route_parse_display() {
let err = AddonLoaderError::RouteParse {
url: "/addons/operate".to_string(),
reason: "missing controller".to_string(),
};
assert!(err.to_string().contains("/addons/operate"));
}
#[test]
fn test_from_io_error() {
let io_err = std::io::Error::other("test");
let err: AddonLoaderError = io_err.into();
assert!(matches!(err, AddonLoaderError::ReadFile { .. }));
}
#[test]
fn test_result_alias_ok() {
let result: AddonLoaderResult<i32> = Ok(42);
match result {
Ok(v) => assert_eq!(v, 42),
Err(_) => panic!("expected Ok"),
}
}
#[test]
fn test_result_alias_err() {
let result: AddonLoaderResult<i32> = Err(AddonLoaderError::AddonNotFound("x".to_string()));
assert!(result.is_err());
}
}