use std::fmt;
use cfg_if::cfg_if;
#[derive(Debug)]
#[non_exhaustive]
pub enum ModuleInfoError {
NotAvailable(String),
NullPointer,
Utf8Error(std::str::Utf8Error),
MalformedJson(String),
MetadataTooLarge(String),
IoError(std::io::Error),
Other(Box<dyn std::error::Error + Send + Sync>),
}
impl fmt::Display for ModuleInfoError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ModuleInfoError::NotAvailable(msg) => write!(f, "Module info not available: {msg}"),
ModuleInfoError::NullPointer => write!(f, "Pointer is null"),
ModuleInfoError::Utf8Error(err) => write!(f, "UTF-8 conversion error: {err}"),
ModuleInfoError::MalformedJson(msg) => write!(f, "Malformed JSON string: {msg}"),
ModuleInfoError::MetadataTooLarge(msg) => {
write!(f, "Metadata size exceeds limit: {msg}")
}
ModuleInfoError::IoError(err) => write!(f, "IO error: {err}"),
ModuleInfoError::Other(err) => write!(f, "Other error: {err}"),
}
}
}
impl std::error::Error for ModuleInfoError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
ModuleInfoError::Utf8Error(err) => Some(err),
ModuleInfoError::IoError(err) => Some(err),
ModuleInfoError::Other(err) => Some(err.as_ref()),
_ => None,
}
}
}
impl From<std::str::Utf8Error> for ModuleInfoError {
fn from(err: std::str::Utf8Error) -> Self {
ModuleInfoError::Utf8Error(err)
}
}
impl From<std::io::Error> for ModuleInfoError {
fn from(err: std::io::Error) -> Self {
ModuleInfoError::IoError(err)
}
}
impl From<std::env::VarError> for ModuleInfoError {
fn from(err: std::env::VarError) -> Self {
ModuleInfoError::Other(Box::new(err))
}
}
cfg_if! {
if #[cfg(target_os = "linux")] {
impl From<toml::de::Error> for ModuleInfoError {
fn from(err: toml::de::Error) -> Self {
ModuleInfoError::Other(Box::new(err))
}
}
impl From<serde_json::Error> for ModuleInfoError {
fn from(err: serde_json::Error) -> Self {
ModuleInfoError::Other(Box::new(err))
}
}
}
}
pub type ModuleInfoResult<T> = Result<T, ModuleInfoError>;
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error as _;
#[test]
fn display_and_source_cover_every_variant() {
let invalid_utf8: Vec<u8> = vec![0xff, 0xfe];
let utf8_err = match std::str::from_utf8(&invalid_utf8) {
Ok(_) => unreachable!("invalid_utf8 is never valid UTF-8"),
Err(e) => e,
};
let cases: Vec<(ModuleInfoError, &str, bool)> = vec![
(
ModuleInfoError::NotAvailable("ctx".into()),
"Module info not available",
false,
),
(ModuleInfoError::NullPointer, "Pointer is null", false),
(
ModuleInfoError::MalformedJson("bad".into()),
"Malformed JSON string",
false,
),
(
ModuleInfoError::MetadataTooLarge("size".into()),
"Metadata size exceeds limit",
false,
),
(
ModuleInfoError::Utf8Error(utf8_err),
"UTF-8 conversion error",
true,
),
(
ModuleInfoError::IoError(std::io::Error::new(
std::io::ErrorKind::NotFound,
"missing",
)),
"IO error",
true,
),
(
ModuleInfoError::Other(std::io::Error::other("boxed").into()),
"Other error",
true,
),
];
for (err, prefix, has_source) in cases {
let rendered = format!("{err}");
assert!(
rendered.starts_with(prefix),
"Display for {err:?} should start with {prefix:?}, got {rendered:?}"
);
assert_eq!(
err.source().is_some(),
has_source,
"source() arm wrong for {err:?}"
);
}
}
#[test]
fn from_impls_wrap_into_correct_variant() {
let invalid_utf8: Vec<u8> = vec![0xff];
let utf8_err = match std::str::from_utf8(&invalid_utf8) {
Ok(_) => unreachable!("invalid_utf8 is never valid UTF-8"),
Err(e) => e,
};
let wrapped: ModuleInfoError = utf8_err.into();
assert!(matches!(wrapped, ModuleInfoError::Utf8Error(_)));
let io_err = std::io::Error::other("x");
let wrapped: ModuleInfoError = io_err.into();
assert!(matches!(wrapped, ModuleInfoError::IoError(_)));
let var_err = std::env::VarError::NotPresent;
let wrapped: ModuleInfoError = var_err.into();
assert!(matches!(wrapped, ModuleInfoError::Other(_)));
}
#[cfg(target_os = "linux")]
#[test]
fn linux_from_impls_wrap_into_other() {
let toml_err = toml::from_str::<toml::Value>("not [valid").unwrap_err();
let wrapped: ModuleInfoError = toml_err.into();
assert!(matches!(wrapped, ModuleInfoError::Other(_)));
let json_err = serde_json::from_str::<serde_json::Value>("not json").unwrap_err();
let wrapped: ModuleInfoError = json_err.into();
assert!(matches!(wrapped, ModuleInfoError::Other(_)));
}
}