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