6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 1 | use std::{fmt, str::FromStr, time::Duration}; |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 2 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 3 | use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 4 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 5 | const KIB: u64 = 1024; |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 6 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 7 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 8 | pub struct Bytes(pub u64); |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 9 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 10 | impl Bytes { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 11 | pub const ZERO: Self = Self(0); |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 12 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 13 | pub const fn gib(n: u64) -> Self { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 14 | Self(n * KIB * KIB * KIB) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 15 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 16 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 17 | pub const fn mib(n: u64) -> Self { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 18 | Self(n * KIB * KIB) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 19 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 20 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 21 | pub const fn saturating_sub(self, other: Self) -> Self { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 22 | Self(self.0.saturating_sub(other.0)) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 23 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 24 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 25 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 26 | impl std::ops::Add for Bytes { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 27 | type Output = Self; |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 28 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 29 | fn add(self, other: Self) -> Self { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 30 | Self(self.0.saturating_add(other.0)) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 31 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 32 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 33 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 34 | impl std::ops::AddAssign for Bytes { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 35 | fn add_assign(&mut self, other: Self) { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 36 | *self = *self + other; |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 37 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 38 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 39 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 40 | impl std::iter::Sum for Bytes { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 41 | fn sum<I: Iterator<Item = Self>>(iter: I) -> Self { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 42 | iter.fold(Self::ZERO, |a, b| a + b) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 43 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 44 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 45 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 46 | fn split_number(s: &str) -> Result<(f64, &str), &'static str> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 47 | let s = s.trim(); |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 48 | let end = s |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 49 | .find(|c: char| !(c.is_ascii_digit() || c == '.')) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 50 | .unwrap_or(s.len()); |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 51 | let (num, suffix) = s.split_at(end); |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 52 | let num = num.parse::<f64>().map_err(|_| "expected a number")?; |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 53 | if !num.is_finite() || num < 0.0 { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 54 | return Err("expected a non-negative number"); |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 55 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 56 | Ok((num, suffix.trim())) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 57 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 58 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 59 | impl FromStr for Bytes { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 60 | type Err = &'static str; |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 61 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 62 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 63 | let (num, suffix) = split_number(s)?; |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 64 | let scale = match suffix.to_ascii_lowercase().as_str() { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 65 | "" | "b" => 1, |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 66 | "k" | "kb" | "kib" => KIB, |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 67 | "m" | "mb" | "mib" => KIB.pow(2), |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 68 | "g" | "gb" | "gib" => KIB.pow(3), |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 69 | "t" | "tb" | "tib" => KIB.pow(4), |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 70 | _ => return Err("expected a size like 512M or 4GiB"), |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 71 | }; |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 72 | Ok(Self((num * scale as f64) as u64)) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 73 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 74 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 75 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 76 | impl fmt::Display for Bytes { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 77 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 78 | const UNITS: [&str; 5] = ["B", "KiB", "MiB", "GiB", "TiB"]; |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 79 | let mut value = self.0 as f64; |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 80 | let mut unit = 0; |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 81 | while value >= 1024.0 && unit < UNITS.len() - 1 { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 82 | value /= 1024.0; |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 83 | unit += 1; |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 84 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 85 | if unit == 0 { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 86 | write!(f, "{}B", self.0) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 87 | } else { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 88 | write!(f, "{value:.1}{}", UNITS[unit]) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 89 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 90 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 91 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 92 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 93 | impl pound::FromArg for Bytes { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 94 | fn from_arg(s: &str) -> Result<Self, pound::ValueError> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 95 | s.parse().map_err(|e| pound::ValueError::new(s, e)) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 96 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 97 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 98 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 99 | impl Serialize for Bytes { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 100 | fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 101 | s.serialize_u64(self.0) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 102 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 103 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 104 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 105 | impl<'de> Deserialize<'de> for Bytes { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 106 | fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 107 | struct V; |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 108 | impl de::Visitor<'_> for V { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 109 | type Value = Bytes; |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 110 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 111 | fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 112 | f.write_str("a byte count or a size string like \"4GiB\"") |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 113 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 114 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 115 | fn visit_u64<E: de::Error>(self, v: u64) -> Result<Bytes, E> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 116 | Ok(Bytes(v)) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 117 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 118 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 119 | fn visit_i64<E: de::Error>(self, v: i64) -> Result<Bytes, E> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 120 | u64::try_from(v) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 121 | .map(Bytes) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 122 | .map_err(|_| E::custom("negative size")) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 123 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 124 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 125 | fn visit_str<E: de::Error>(self, v: &str) -> Result<Bytes, E> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 126 | v.parse().map_err(E::custom) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 127 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 128 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 129 | d.deserialize_any(V) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 130 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 131 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 132 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 133 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 134 | pub struct Dur(pub Duration); |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 135 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 136 | impl Dur { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 137 | pub const fn secs(n: u64) -> Self { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 138 | Self(Duration::from_secs(n)) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 139 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 140 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 141 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 142 | impl FromStr for Dur { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 143 | type Err = &'static str; |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 144 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 145 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 146 | let mut rest = s.trim(); |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 147 | if rest.is_empty() { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 148 | return Err("expected a duration like 90s or 5m"); |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 149 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 150 | let mut total = 0.0; |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 151 | while !rest.is_empty() { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 152 | let (num, tail) = split_number(rest)?; |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 153 | let unit_len = tail |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 154 | .find(|c: char| c.is_ascii_digit() || c == '.') |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 155 | .unwrap_or(tail.len()); |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 156 | let (unit, next) = tail.split_at(unit_len); |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 157 | let scale = match unit.trim() { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 158 | "" | "s" | "sec" | "secs" => 1.0, |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 159 | "ms" => 0.001, |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 160 | "m" | "min" | "mins" => 60.0, |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 161 | "h" | "hr" | "hrs" => 3600.0, |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 162 | "d" | "day" | "days" => 86400.0, |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 163 | _ => return Err("expected a duration like 90s or 5m"), |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 164 | }; |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 165 | total = num.mul_add(scale, total); |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 166 | rest = next.trim(); |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 167 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 168 | Ok(Self(Duration::from_secs_f64(total))) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 169 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 170 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 171 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 172 | impl fmt::Display for Dur { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 173 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 174 | let s = self.0.as_secs(); |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 175 | match s { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 176 | 0..60 => write!(f, "{s}s"), |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 177 | 60..3600 if s.is_multiple_of(60) => write!(f, "{}m", s / 60), |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 178 | 60..3600 => write!(f, "{}m{}s", s / 60, s % 60), |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 179 | _ if s.is_multiple_of(3600) => write!(f, "{}h", s / 3600), |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 180 | _ => write!(f, "{}h{}m", s / 3600, (s % 3600) / 60), |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 181 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 182 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 183 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 184 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 185 | impl pound::FromArg for Dur { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 186 | fn from_arg(s: &str) -> Result<Self, pound::ValueError> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 187 | s.parse().map_err(|e| pound::ValueError::new(s, e)) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 188 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 189 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 190 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 191 | impl Serialize for Dur { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 192 | fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 193 | s.serialize_u64(self.0.as_secs()) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 194 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 195 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 196 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 197 | impl<'de> Deserialize<'de> for Dur { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 198 | fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 199 | struct V; |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 200 | impl de::Visitor<'_> for V { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 201 | type Value = Dur; |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 202 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 203 | fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 204 | f.write_str("seconds or a duration string like \"5m\"") |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 205 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 206 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 207 | fn visit_u64<E: de::Error>(self, v: u64) -> Result<Dur, E> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 208 | Ok(Dur::secs(v)) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 209 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 210 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 211 | fn visit_i64<E: de::Error>(self, v: i64) -> Result<Dur, E> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 212 | u64::try_from(v) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 213 | .map(Dur::secs) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 214 | .map_err(|_| E::custom("negative duration")) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 215 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 216 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 217 | fn visit_str<E: de::Error>(self, v: &str) -> Result<Dur, E> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 218 | v.parse().map_err(E::custom) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 219 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 220 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 221 | d.deserialize_any(V) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 222 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 223 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 224 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 225 | #[derive(Clone, Copy, Debug, PartialEq)] |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 226 | pub enum Amount<T> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 227 | Abs(T), |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 228 | Percent(f64), |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 229 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 230 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 231 | impl Amount<Bytes> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 232 | pub fn resolve(self, total: Bytes) -> Bytes { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 233 | match self { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 234 | Self::Abs(b) => b, |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 235 | Self::Percent(p) => Bytes((total.0 as f64 * p / 100.0) as u64), |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 236 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 237 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 238 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 239 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 240 | impl Amount<u32> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 241 | pub fn resolve(self, total: u32) -> u32 { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 242 | match self { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 243 | Self::Abs(n) => n, |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 244 | Self::Percent(p) => ((f64::from(total) * p / 100.0).round() as u32).max(1), |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 245 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 246 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 247 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 248 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 249 | impl<T: FromStr> FromStr for Amount<T> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 250 | type Err = &'static str; |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 251 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 252 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 253 | if let Some(p) = s.trim().strip_suffix('%') { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 254 | let p = p |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 255 | .trim() |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 256 | .parse::<f64>() |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 257 | .map_err(|_| "expected a percentage")?; |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 258 | if !(0.0..=100.0).contains(&p) { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 259 | return Err("percentage must be between 0 and 100"); |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 260 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 261 | Ok(Self::Percent(p)) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 262 | } else { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 263 | s.trim() |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 264 | .parse() |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 265 | .map(Self::Abs) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 266 | .map_err(|_| "expected an amount or a percentage") |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 267 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 268 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 269 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 270 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 271 | impl<T: FromStr> pound::FromArg for Amount<T> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 272 | fn from_arg(s: &str) -> Result<Self, pound::ValueError> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 273 | s.parse().map_err(|e| pound::ValueError::new(s, e)) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 274 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 275 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 276 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 277 | impl<'de, T: FromStr> Deserialize<'de> for Amount<T> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 278 | fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 279 | struct V<T>(std::marker::PhantomData<T>); |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 280 | impl<T: FromStr> de::Visitor<'_> for V<T> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 281 | type Value = Amount<T>; |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 282 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 283 | fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 284 | f.write_str("an amount or a percentage string like \"75%\"") |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 285 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 286 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 287 | fn visit_u64<E: de::Error>(self, v: u64) -> Result<Amount<T>, E> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 288 | self.visit_str(&v.to_string()) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 289 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 290 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 291 | fn visit_i64<E: de::Error>(self, v: i64) -> Result<Amount<T>, E> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 292 | self.visit_str(&v.to_string()) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 293 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 294 | |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 295 | fn visit_str<E: de::Error>(self, v: &str) -> Result<Amount<T>, E> { |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 296 | v.parse().map_err(E::custom) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 297 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 298 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 299 | d.deserialize_any(V(std::marker::PhantomData)) |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 300 | } |
6f5b84fad3ef | atagen | 2026-09-27 03:59 UTC | 301 | } |