use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetricDetail {
pub value: f32,
pub score: f32,
}
#[cfg(target_os = "linux")]
use crate::linux::calculator::LinuxProvider;
#[cfg(target_os = "macos")]
use crate::macos::calculator::MacProvider;
pub mod error;
#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "macos")]
mod macos;
mod sigmoid;
pub use error::{PwrzvError, PwrzvResult};
trait PowerReserveMeterProvider {
async fn get_power_reserve_level(&self) -> PwrzvResult<f32>;
async fn get_power_reserve_level_with_details(
&self,
) -> PwrzvResult<(f32, HashMap<String, MetricDetail>)>;
}
#[derive(Clone, Debug)]
enum Calculator {
#[cfg(target_os = "linux")]
Linux(LinuxProvider),
#[cfg(target_os = "macos")]
MacOS(MacProvider),
}
impl Calculator {
fn new() -> PwrzvResult<Self> {
#[cfg(target_os = "linux")]
{
Ok(Calculator::Linux(LinuxProvider))
}
#[cfg(target_os = "macos")]
{
Ok(Calculator::MacOS(MacProvider))
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
{
Err(PwrzvError::unsupported_platform(&format!(
"Platform '{}' is not supported yet. Only Linux and macOS are supported for now.",
std::env::consts::OS
)))
}
}
async fn get_power_reserve_level(&self) -> PwrzvResult<f32> {
#[cfg(target_os = "linux")]
{
let Calculator::Linux(calc) = self;
return calc.get_power_reserve_level().await;
}
#[cfg(target_os = "macos")]
{
let Calculator::MacOS(calc) = self;
return calc.get_power_reserve_level().await;
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
unreachable!("Calculator should only be created on supported platforms")
}
async fn get_power_reserve_level_with_details(
&self,
) -> PwrzvResult<(f32, HashMap<String, MetricDetail>)> {
#[cfg(target_os = "linux")]
{
let Calculator::Linux(calc) = self;
return calc.get_power_reserve_level_with_details().await;
}
#[cfg(target_os = "macos")]
{
let Calculator::MacOS(calc) = self;
return calc.get_power_reserve_level_with_details().await;
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
unreachable!("Calculator should only be created on supported platforms")
}
}
pub fn get_platform_name() -> &'static str {
std::env::consts::OS
}
pub async fn get_power_reserve_level_direct() -> PwrzvResult<f32> {
let calculator = Calculator::new()?;
calculator.get_power_reserve_level().await
}
pub async fn get_power_reserve_level_with_details_direct()
-> PwrzvResult<(f32, HashMap<String, MetricDetail>)> {
let calculator = Calculator::new()?;
calculator.get_power_reserve_level_with_details().await
}
pub fn check_platform() -> PwrzvResult<()> {
Calculator::new().map(|_| ())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_calculator_creation() {
let calculator = Calculator::new();
assert!(
calculator.is_ok(),
"Calculator creation should succeed on supported platforms"
);
}
#[test]
fn test_get_platform_name() {
let platform = get_platform_name();
assert!(!platform.is_empty(), "Platform name should not be empty");
assert!(
platform == "linux" || platform == "macos",
"Platform should be Linux or macOS, got: {platform}"
);
}
#[test]
fn test_check_platform() {
let result = check_platform();
assert!(
result.is_ok(),
"Platform check should succeed on supported platforms"
);
}
#[tokio::test]
async fn test_get_power_reserve_level_direct() {
let result = get_power_reserve_level_direct().await;
assert!(
result.is_ok(),
"get_power_reserve_level_direct should succeed"
);
let level = result.unwrap();
assert!(
level > 0.0 && level <= 5.0,
"Level should be in range (0.0, 5.0], got: {level}"
);
}
#[tokio::test]
async fn test_get_power_reserve_level_with_details_direct() {
let result = get_power_reserve_level_with_details_direct().await;
assert!(
result.is_ok(),
"get_power_reserve_level_with_details_direct should succeed"
);
let (level, details) = result.unwrap();
assert!(
level > 0.0 && level <= 5.0,
"Level should be in range (0.0, 5.0], got: {level}"
);
for (key, detail) in &details {
assert!(
detail.score > 0.0 && detail.score <= 5.0,
"Score for '{key}' should be in range (0.0, 5.0], got: {}",
detail.score
);
}
}
#[test]
fn test_precision_levels() {
let level_1 = 4.23f32;
let level_2 = 4.24f32;
assert_ne!(
level_1, level_2,
"Should be able to distinguish precision levels"
);
assert!((1.0..=5.0).contains(&level_1));
assert!((1.0..=5.0).contains(&level_2));
}
}