1
Fork 0
mirror of https://github.com/RGBCube/superfreq synced 2025-07-27 17:07:44 +00:00

treewide: streamline error handling; leverage thiserror and anyhow

This commit is contained in:
NotAShelf 2025-05-18 15:39:49 +03:00
parent 99fbfc0ea7
commit 32422f2b4f
No known key found for this signature in database
GPG key ID: 29D95B64378DB4BF
7 changed files with 81 additions and 202 deletions

View file

@ -98,37 +98,18 @@ pub struct AppConfig {
}
// Error type for config loading
#[derive(Debug)]
#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
Io(std::io::Error),
Toml(toml::de::Error),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("TOML parsing error: {0}")]
Toml(#[from] toml::de::Error),
#[error("Configuration validation error: {0}")]
Validation(String),
}
impl From<std::io::Error> for ConfigError {
fn from(err: std::io::Error) -> Self {
Self::Io(err)
}
}
impl From<toml::de::Error> for ConfigError {
fn from(err: toml::de::Error) -> Self {
Self::Toml(err)
}
}
impl std::fmt::Display for ConfigError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Io(e) => write!(f, "I/O error: {e}"),
Self::Toml(e) => write!(f, "TOML parsing error: {e}"),
Self::Validation(s) => write!(f, "Configuration validation error: {s}"),
}
}
}
impl std::error::Error for ConfigError {}
// Intermediate structs for TOML parsing
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ProfileConfigToml {

View file

@ -109,7 +109,7 @@ pub fn set_governor(governor: &str, core_id: Option<u32>) -> Result<()> {
let (is_valid, available_governors) = is_governor_valid(governor)?;
if !is_valid {
return Err(ControlError::InvalidValueError(format!(
return Err(ControlError::InvalidGovernor(format!(
"Governor '{}' is not available on this system. Valid governors: {}",
governor,
available_governors.join(", ")
@ -432,7 +432,7 @@ fn read_sysfs_value_as_u32(path: &str) -> Result<u32> {
content
.trim()
.parse::<u32>()
.map_err(|e| ControlError::ReadError(format!("Failed to parse value from {path}: {e}")))
.map_err(|e| ControlError::ParseError(format!("Failed to parse value from {path}: {e}")))
}
fn validate_min_frequency(core_id: u32, new_min_freq_mhz: u32) -> Result<()> {

View file

@ -61,7 +61,10 @@ fn idle_multiplier(idle_secs: u64) -> f32 {
/// Calculate optimal polling interval based on system conditions and history
///
/// Returns Ok with the calculated interval, or Err if the configuration is invalid
fn compute_new(params: &IntervalParams, system_history: &SystemHistory) -> Result<u64, ControlError> {
fn compute_new(
params: &IntervalParams,
system_history: &SystemHistory,
) -> Result<u64, ControlError> {
// Use the centralized validation function
validate_poll_intervals(params.min_interval, params.max_interval)?;
@ -134,8 +137,8 @@ fn compute_new(params: &IntervalParams, system_history: &SystemHistory) -> Resul
const TOTAL_WEIGHT: u128 = PREVIOUS_VALUE_WEIGHT + NEW_VALUE_WEIGHT; // 10
// XXX: Use u128 arithmetic to avoid overflow with large interval values
let result = (cached as u128 * PREVIOUS_VALUE_WEIGHT
+ new_interval as u128 * NEW_VALUE_WEIGHT)
let result = (u128::from(cached) * PREVIOUS_VALUE_WEIGHT
+ u128::from(new_interval) * NEW_VALUE_WEIGHT)
/ TOTAL_WEIGHT;
result as u64
@ -381,20 +384,19 @@ impl SystemHistory {
fn validate_poll_intervals(min_interval: u64, max_interval: u64) -> Result<(), ControlError> {
if min_interval < 1 {
return Err(ControlError::InvalidValueError(
"min_interval must be ≥ 1".to_string()
"min_interval must be ≥ 1".to_string(),
));
}
if max_interval < 1 {
return Err(ControlError::InvalidValueError(
"max_interval must be ≥ 1".to_string()
"max_interval must be ≥ 1".to_string(),
));
}
if max_interval >= min_interval {
Ok(())
} else {
Err(ControlError::InvalidValueError(format!(
"Invalid interval configuration: max_interval ({}) is less than min_interval ({})",
max_interval, min_interval
"Invalid interval configuration: max_interval ({max_interval}) is less than min_interval ({min_interval})"
)))
}
}

View file

@ -207,7 +207,7 @@ pub fn determine_and_apply_settings(
// Let set_governor handle the validation
if let Err(e) = cpu::set_governor(governor, None) {
// If the governor is not available, log a warning
if matches!(e, ControlError::InvalidValueError(_))
if matches!(e, ControlError::InvalidGovernor(_))
|| matches!(e, ControlError::NotSupported(_))
{
warn!(

View file

@ -1,194 +1,80 @@
use std::io;
#[derive(Debug)]
#[derive(Debug, thiserror::Error)]
pub enum ControlError {
Io(io::Error),
#[error("I/O error: {0}")]
Io(#[from] io::Error),
#[error("Failed to write to sysfs path: {0}")]
WriteError(String),
#[error("Failed to read sysfs path: {0}")]
ReadError(String),
#[error("Invalid value for setting: {0}")]
InvalidValueError(String),
#[error("Control action not supported: {0}")]
NotSupported(String),
#[error("Permission denied: {0}. Try running with sudo.")]
PermissionDenied(String),
#[error("Invalid platform control profile {0} supplied, please provide a valid one.")]
InvalidProfile(String),
#[error("Invalid governor: {0}")]
InvalidGovernor(String),
#[error("Failed to parse value: {0}")]
ParseError(String),
#[error("Path missing: {0}")]
PathMissing(String),
}
impl From<io::Error> for ControlError {
fn from(err: io::Error) -> Self {
match err.kind() {
io::ErrorKind::PermissionDenied => Self::PermissionDenied(err.to_string()),
_ => Self::Io(err),
}
}
}
impl std::fmt::Display for ControlError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Io(e) => write!(f, "I/O error: {e}"),
Self::WriteError(s) => write!(f, "Failed to write to sysfs path: {s}"),
Self::ReadError(s) => write!(f, "Failed to read sysfs path: {s}"),
Self::InvalidValueError(s) => write!(f, "Invalid value for setting: {s}"),
Self::NotSupported(s) => write!(f, "Control action not supported: {s}"),
Self::PermissionDenied(s) => {
write!(f, "Permission denied: {s}. Try running with sudo.")
}
Self::InvalidProfile(s) => {
write!(
f,
"Invalid platform control profile {s} supplied, please provide a valid one."
)
}
Self::InvalidGovernor(s) => {
write!(f, "Invalid governor: {s}")
}
Self::ParseError(s) => {
write!(f, "Failed to parse value: {s}")
}
Self::PathMissing(s) => {
write!(f, "Path missing: {s}")
}
}
}
}
impl std::error::Error for ControlError {}
#[derive(Debug)]
#[derive(Debug, thiserror::Error)]
pub enum SysMonitorError {
Io(io::Error),
#[error("I/O error: {0}")]
Io(#[from] io::Error),
#[error("Failed to read sysfs path: {0}")]
ReadError(String),
#[error("Failed to parse value: {0}")]
ParseError(String),
#[error("Failed to parse /proc/stat: {0}")]
ProcStatParseError(String),
}
impl From<io::Error> for SysMonitorError {
fn from(err: io::Error) -> Self {
Self::Io(err)
}
}
impl std::fmt::Display for SysMonitorError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Io(e) => write!(f, "I/O error: {e}"),
Self::ReadError(s) => write!(f, "Failed to read sysfs path: {s}"),
Self::ParseError(s) => write!(f, "Failed to parse value: {s}"),
Self::ProcStatParseError(s) => {
write!(f, "Failed to parse /proc/stat: {s}")
}
}
}
}
impl std::error::Error for SysMonitorError {}
#[derive(Debug)]
#[derive(Debug, thiserror::Error)]
pub enum EngineError {
ControlError(ControlError),
#[error("CPU control error: {0}")]
ControlError(#[from] ControlError),
#[error("Configuration error: {0}")]
ConfigurationError(String),
}
impl From<ControlError> for EngineError {
fn from(err: ControlError) -> Self {
Self::ControlError(err)
}
}
impl std::fmt::Display for EngineError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ControlError(e) => write!(f, "CPU control error: {e}"),
Self::ConfigurationError(s) => write!(f, "Configuration error: {s}"),
}
}
}
impl std::error::Error for EngineError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::ControlError(e) => Some(e),
Self::ConfigurationError(_) => None,
}
}
}
// A unified error type for the entire application
#[derive(Debug)]
#[derive(Debug, thiserror::Error)]
pub enum AppError {
Control(ControlError),
Monitor(SysMonitorError),
Engine(EngineError),
Config(crate::config::ConfigError),
#[error("{0}")]
Control(#[from] ControlError),
#[error("{0}")]
Monitor(#[from] SysMonitorError),
#[error("{0}")]
Engine(#[from] EngineError),
#[error("{0}")]
Config(#[from] crate::config::ConfigError),
#[error("{0}")]
Generic(String),
Io(io::Error),
}
impl From<ControlError> for AppError {
fn from(err: ControlError) -> Self {
Self::Control(err)
}
}
impl From<SysMonitorError> for AppError {
fn from(err: SysMonitorError) -> Self {
Self::Monitor(err)
}
}
impl From<EngineError> for AppError {
fn from(err: EngineError) -> Self {
Self::Engine(err)
}
}
impl From<crate::config::ConfigError> for AppError {
fn from(err: crate::config::ConfigError) -> Self {
Self::Config(err)
}
}
impl From<io::Error> for AppError {
fn from(err: io::Error) -> Self {
Self::Io(err)
}
}
impl From<String> for AppError {
fn from(err: String) -> Self {
Self::Generic(err)
}
}
impl From<&str> for AppError {
fn from(err: &str) -> Self {
Self::Generic(err.to_string())
}
}
impl std::fmt::Display for AppError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Control(e) => write!(f, "{e}"),
Self::Monitor(e) => write!(f, "{e}"),
Self::Engine(e) => write!(f, "{e}"),
Self::Config(e) => write!(f, "{e}"),
Self::Generic(s) => write!(f, "{s}"),
Self::Io(e) => write!(f, "I/O error: {e}"),
}
}
}
impl std::error::Error for AppError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Control(e) => Some(e),
Self::Monitor(e) => Some(e),
Self::Engine(e) => Some(e),
Self::Config(e) => Some(e),
Self::Generic(_) => None,
Self::Io(e) => Some(e),
}
}
#[error("I/O error: {0}")]
Io(#[from] io::Error),
}