use serde::Deserialize;
#[derive(Debug, Default, Deserialize)]
pub(crate) struct RateLimits {
#[serde(default)]
pub(crate) five_hour: Option<RateWindow>,
#[serde(default)]
pub(crate) seven_day: Option<RateWindow>,
}
#[derive(Debug, Default, Deserialize)]
pub(crate) struct RateWindow {
#[serde(default)]
pub(crate) used_percentage: Option<f64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum UsageColor {
Normal,
Amber,
Red,
}
pub(crate) fn usage_color(pct: f64) -> UsageColor {
let pct = pct.clamp(0.0, 100.0);
if pct >= 80.0 {
UsageColor::Red
} else if pct >= 50.0 {
UsageColor::Amber
} else {
UsageColor::Normal
}
}
pub(crate) fn colorize_usage(text: &str, pct: f64) -> String {
match usage_color(pct) {
UsageColor::Red => format!("\u{1b}[31m{text}\u{1b}[0m"),
UsageColor::Amber => format!("\u{1b}[33m{text}\u{1b}[0m"),
UsageColor::Normal => text.to_string(),
}
}
pub(crate) fn usage_segment(rate_limits: Option<&RateLimits>) -> Option<String> {
let rate_limits = rate_limits?;
let five_hour = rate_limits
.five_hour
.as_ref()
.and_then(|w| w.used_percentage)
.map(|pct| colorize_usage(&format!("\u{23f3}{}%", pct.round() as i64), pct));
let seven_day = rate_limits
.seven_day
.as_ref()
.and_then(|w| w.used_percentage)
.map(|pct| colorize_usage(&format!("\u{1f4c5}{}%", pct.round() as i64), pct));
let parts: Vec<String> = [five_hour, seven_day].into_iter().flatten().collect();
if parts.is_empty() {
None
} else {
Some(parts.join(" "))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn window(pct: f64) -> RateWindow {
RateWindow {
used_percentage: Some(pct),
}
}
#[test]
fn usage_segment_both_present() {
let rl = RateLimits {
five_hour: Some(window(24.0)),
seven_day: Some(window(41.0)),
};
let seg = usage_segment(Some(&rl)).expect("segment");
assert_eq!(seg, "\u{23f3}24% \u{1f4c5}41%");
}
#[test]
fn usage_segment_only_five_hour() {
let rl = RateLimits {
five_hour: Some(window(24.0)),
seven_day: None,
};
let seg = usage_segment(Some(&rl)).expect("segment");
assert_eq!(seg, "\u{23f3}24%");
}
#[test]
fn usage_segment_only_seven_day() {
let rl = RateLimits {
five_hour: None,
seven_day: Some(window(41.0)),
};
let seg = usage_segment(Some(&rl)).expect("segment");
assert_eq!(seg, "\u{1f4c5}41%");
}
#[test]
fn usage_segment_none_when_absent() {
assert_eq!(usage_segment(None), None);
let rl = RateLimits {
five_hour: None,
seven_day: None,
};
assert_eq!(usage_segment(Some(&rl)), None);
let rl = RateLimits {
five_hour: Some(RateWindow {
used_percentage: None,
}),
seven_day: None,
};
assert_eq!(usage_segment(Some(&rl)), None);
}
#[test]
fn usage_color_boundaries() {
assert_eq!(usage_color(0.0), UsageColor::Normal);
assert_eq!(usage_color(49.0), UsageColor::Normal);
assert_eq!(usage_color(49.999), UsageColor::Normal);
assert_eq!(usage_color(50.0), UsageColor::Amber);
assert_eq!(usage_color(79.0), UsageColor::Amber);
assert_eq!(usage_color(79.999), UsageColor::Amber);
assert_eq!(usage_color(80.0), UsageColor::Red);
assert_eq!(usage_color(100.0), UsageColor::Red);
}
#[test]
fn usage_color_clamps_out_of_range() {
assert_eq!(usage_color(-10.0), UsageColor::Normal);
assert_eq!(usage_color(150.0), UsageColor::Red);
}
#[test]
fn colorize_usage_normal() {
let seg = colorize_usage("\u{23f3}24%", 24.0);
assert_eq!(seg, "\u{23f3}24%", "no ANSI wrap below 50%");
}
#[test]
fn colorize_usage_amber() {
let seg = colorize_usage("\u{23f3}62%", 62.0);
assert_eq!(seg, "\u{1b}[33m\u{23f3}62%\u{1b}[0m");
}
#[test]
fn colorize_usage_red() {
let seg = colorize_usage("\u{1f4c5}85%", 85.0);
assert_eq!(seg, "\u{1b}[31m\u{1f4c5}85%\u{1b}[0m");
}
}