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