matchmaker/utils/
percentage.rs1use std::fmt;
2
3use cli_boilerplate_automation::define_restricted_wrapper;
4use serde::{Deserialize, Deserializer};
5
6define_restricted_wrapper!(
7 #[derive(Clone)]
8 Percentage: u16 = 100
9);
10impl Percentage {
11 pub fn new(value: u16) -> Self {
12 if value <= 100 { Self(value) } else { Self(100) }
13 }
14
15 pub fn compute_clamped(&self, total: u16, min: u16, max: u16) -> u16 {
16 let pct_height = (total * self.inner()).div_ceil(100);
17 pct_height.clamp(min, if max == 0 { total } else { max })
18 }
19
20 pub fn complement(&self) -> Self {
21 Self(100 - self.0)
22 }
23}
24
25impl fmt::Display for Percentage {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 write!(f, "{}%", self.0)
28 }
29}
30
31impl TryFrom<u16> for Percentage {
32 type Error = String;
33
34 fn try_from(value: u16) -> Result<Self, Self::Error> {
35 if value > 100 {
36 Err(format!("Percentage out of range: {}", value))
37 } else {
38 Ok(Self::new(value))
39 }
40 }
41}
42impl<'de> Deserialize<'de> for Percentage {
43 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
44 where
45 D: Deserializer<'de>,
46 {
47 let v = u16::deserialize(deserializer)?;
48 v.try_into().map_err(serde::de::Error::custom)
49 }
50}
51impl std::str::FromStr for Percentage {
52 type Err = String;
53
54 fn from_str(s: &str) -> Result<Self, Self::Err> {
55 let s = s.trim_end_matches('%'); let v: u16 = s
57 .parse()
58 .map_err(|e: std::num::ParseIntError| format!("Invalid number: {}", e))?;
59 v.try_into()
60 }
61}