pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("{sensor} is unavailable: {detail}")]
SensorUnavailable {
sensor: &'static str,
detail: String,
},
#[error("{sensor} requires elevated privileges: {detail}")]
PermissionDenied {
sensor: &'static str,
detail: String,
},
#[error("invalid configuration: {0}")]
Config(String),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
}
impl Error {
pub fn sensor(sensor: &'static str, detail: impl Into<String>) -> Self {
Error::SensorUnavailable {
sensor,
detail: detail.into(),
}
}
pub fn permission(sensor: &'static str, detail: impl Into<String>) -> Self {
Error::PermissionDenied {
sensor,
detail: detail.into(),
}
}
pub fn config(detail: impl Into<String>) -> Self {
Error::Config(detail.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error as _;
#[test]
fn every_variant_renders_its_documented_message() {
assert_eq!(
Error::sensor("Intel RAPL", "no powercap domains").to_string(),
"Intel RAPL is unavailable: no powercap domains"
);
assert_eq!(
Error::permission("powermetrics", "must run as root").to_string(),
"powermetrics requires elevated privileges: must run as root"
);
assert_eq!(
Error::config("no power file given").to_string(),
"invalid configuration: no power file given"
);
let io = Error::from(std::io::Error::other("disk on fire"));
assert_eq!(io.to_string(), "I/O error: disk on fire");
}
#[test]
fn only_the_io_variant_carries_a_source() {
assert!(
Error::from(std::io::Error::other("boom"))
.source()
.is_some()
);
assert!(Error::sensor("Intel RAPL", "boom").source().is_none());
}
#[test]
fn an_io_failure_keeps_its_kind_through_the_crate_error() {
let wrapped = Error::from(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
"nope",
));
let Error::Io(inner) = &wrapped else {
panic!("expected Error::Io, got {wrapped:?}");
};
assert_eq!(inner.kind(), std::io::ErrorKind::PermissionDenied);
}
}