compose_lens/model/
cpu_percent.rs1#[derive(Debug, Clone, PartialEq, Eq)]
5#[non_exhaustive]
6pub enum CpuPercent {
7 YamlInteger(String),
9 String(String),
11 OutOfRangeYamlInteger(String),
13}
14
15impl CpuPercent {
16 pub(crate) fn yaml_integer(raw: String) -> Self {
17 if yaml_integer_in_percent_range(&raw) {
18 Self::YamlInteger(raw)
19 } else {
20 Self::OutOfRangeYamlInteger(raw)
21 }
22 }
23
24 #[must_use]
26 pub const fn is_valid(&self) -> bool {
27 !matches!(self, Self::OutOfRangeYamlInteger(_))
28 }
29
30 pub(crate) fn yaml_integer_spelling(value: &str) -> bool {
31 let digits = value
32 .strip_prefix('+')
33 .or_else(|| value.strip_prefix('-'))
34 .unwrap_or(value);
35 let (radix, digits) = if let Some(value) = digits.strip_prefix("0b") {
36 (2, value)
37 } else if let Some(value) = digits.strip_prefix("0o") {
38 (8, value)
39 } else if let Some(value) = digits.strip_prefix("0x") {
40 (16, value)
41 } else {
42 (10, digits)
43 };
44 let mut saw_digit = false;
45 let mut previous_separator = false;
46 for byte in digits.bytes() {
47 if byte == b'_' {
48 if !saw_digit || previous_separator {
49 return false;
50 }
51 previous_separator = true;
52 } else if if radix == 16 {
53 byte.is_ascii_hexdigit()
54 } else {
55 byte.is_ascii_digit() && (byte - b'0') < radix
56 } {
57 saw_digit = true;
58 previous_separator = false;
59 } else {
60 return false;
61 }
62 }
63 saw_digit && !previous_separator
64 }
65}
66
67fn yaml_integer_in_percent_range(value: &str) -> bool {
68 let (negative, value) = match value.strip_prefix('-') {
69 Some(value) => (true, value),
70 None => (false, value.strip_prefix('+').unwrap_or(value)),
71 };
72 let (radix, digits) = if let Some(value) = value.strip_prefix("0b") {
73 (2_u16, value)
74 } else if let Some(value) = value.strip_prefix("0o") {
75 (8_u16, value)
76 } else if let Some(value) = value.strip_prefix("0x") {
77 (16_u16, value)
78 } else {
79 (10_u16, value)
80 };
81
82 let mut result = 0_u16;
83 let mut nonzero = false;
84 for byte in digits.bytes().filter(|byte| *byte != b'_') {
85 let digit = if byte.is_ascii_digit() {
86 u16::from(byte - b'0')
87 } else {
88 u16::from(byte.to_ascii_lowercase() - b'a' + 10)
89 };
90 nonzero |= digit != 0;
91 result = result.saturating_mul(radix).saturating_add(digit);
92 if result > 100 {
93 return false;
94 }
95 }
96 !negative || !nonzero
97}
98
99#[cfg(test)]
100mod tests {
101 use super::CpuPercent;
102
103 #[test]
104 fn classifies_unbounded_yaml_integer_spellings_without_conversion() {
105 for value in ["0", "-0", "-0x0", "+100", "0b110_0100", "0o1_44", "0x64"] {
106 assert!(matches!(
107 CpuPercent::yaml_integer(value.to_owned()),
108 CpuPercent::YamlInteger(actual) if actual == value
109 ));
110 assert!(CpuPercent::yaml_integer_spelling(value));
111 }
112 for value in ["-1", "101", "0x65", "999999999999999999999999999999"] {
113 assert!(matches!(
114 CpuPercent::yaml_integer(value.to_owned()),
115 CpuPercent::OutOfRangeYamlInteger(actual) if actual == value
116 ));
117 }
118 for value in ["1.0", "1e3", "0x_1", "1_"] {
119 assert!(!CpuPercent::yaml_integer_spelling(value));
120 }
121 }
122}