1use crate::error::{BytesError, BytesResult};
2
3pub fn format_bytes(bytes: u64, precision: usize) -> String {
28 let unit = 1024.0;
29 let sizes = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"];
30
31 let idx = (bytes as f64).log(unit).floor() as usize;
32 let idx = idx.min(sizes.len() - 1);
33
34 format!(
35 "{:.*} {}",
36 precision,
37 bytes as f64 / unit.powi(idx.try_into().unwrap()),
38 sizes[idx]
39 )
40}
41
42pub fn parse_bytes(s: &str) -> BytesResult<u64> {
69 let mut size = s.trim().to_uppercase();
70
71 if let Ok(v) = size.parse::<u64>() {
73 return Ok(v);
74 };
75
76 let prefixes = ["", "K", "M", "G", "T", "P", "E"];
77
78 let base: f64 = if size.ends_with("IB") {
79 size.truncate(size.len() - 2);
80 1024.0
81 } else if size.ends_with("B") {
82 size.truncate(size.len() - 1);
83 1000.0
84 } else {
85 return Err(BytesError::ParseFailed {
86 input: s.to_string(),
87 reason: "Invalid suffix".to_string(),
88 });
89 };
90
91 prefixes
92 .iter()
93 .enumerate()
94 .rev()
95 .find_map(|(i, p)| {
96 size.strip_suffix(p).and_then(|num| {
97 num.trim()
98 .parse::<f64>()
99 .ok()
100 .map(|n| n * base.powi(i.try_into().unwrap()))
101 .map(|n| n.round() as u64)
102 })
103 })
104 .ok_or_else(|| {
105 BytesError::ParseFailed {
106 input: s.to_string(),
107 reason: "Unrecognized size format".into(),
108 }
109 })
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn test_format_bytes_with_precisions() {
118 assert_eq!(format_bytes(1111, 0), "1 KiB");
119
120 assert_eq!(format_bytes(0, 0), "0 B");
121 assert_eq!(format_bytes(0, 3), "0.000 B");
122 assert_eq!(format_bytes(1023, 0), "1023 B");
123 assert_eq!(format_bytes(1023, 2), "1023.00 B");
124
125 assert_eq!(format_bytes(1024, 0), "1 KiB");
126 assert_eq!(format_bytes(1024, 1), "1.0 KiB");
127 assert_eq!(format_bytes(1536, 2), "1.50 KiB");
128 assert_eq!(format_bytes(2047, 3), "1.999 KiB");
129 assert_eq!(format_bytes(2048, 4), "2.0000 KiB");
130
131 assert_eq!(format_bytes(1024_u64.pow(2), 0), "1 MiB");
132 assert_eq!(format_bytes(3 * 1024_u64.pow(2) / 2, 2), "1.50 MiB");
133 assert_eq!(format_bytes(2 * 1024_u64.pow(2) - 1, 3), "2.000 MiB");
134
135 assert_eq!(format_bytes(1024_u64.pow(3), 2), "1.00 GiB");
136 assert_eq!(format_bytes(5 * 1024_u64.pow(3) / 2, 1), "2.5 GiB");
137
138 assert_eq!(format_bytes(1024_u64.pow(4), 3), "1.000 TiB");
139 assert_eq!(format_bytes(3 * 1024_u64.pow(4) / 2, 2), "1.50 TiB");
140
141 assert_eq!(format_bytes(1024_u64.pow(5), 0), "1 PiB");
142 assert_eq!(
143 format_bytes(1024_u64.pow(5) + 512 * 1024_u64.pow(4), 2),
144 "1.50 PiB"
145 );
146
147 assert_eq!(format_bytes(1024_u64.pow(6), 1), "1.0 EiB");
148 assert_eq!(
149 format_bytes(1024_u64.pow(6) + 512 * 1024_u64.pow(5), 3),
150 "1.500 EiB"
151 );
152 }
153
154 #[test]
155 fn test_parse_bytes() {
156 assert_eq!(parse_bytes("111").unwrap(), 111);
157
158 assert_eq!(parse_bytes("42").unwrap(), 42);
159 assert_eq!(parse_bytes(" 120 ").unwrap(), 120);
160
161 assert_eq!(parse_bytes("0B").unwrap(), 0);
162 assert_eq!(parse_bytes("1B").unwrap(), 1);
163 assert_eq!(parse_bytes("1023B").unwrap(), 1023);
164
165 assert_eq!(parse_bytes("1KiB").unwrap(), 1024);
166 assert_eq!(parse_bytes("1.50KiB").unwrap(), 3 * 1024 / 2);
167 assert_eq!(parse_bytes("1KB").unwrap(), 1000);
168 assert_eq!(parse_bytes("1.50KB").unwrap(), 3 * 1000 / 2);
169
170 assert_eq!(parse_bytes("1MiB").unwrap(), 1024_u64.pow(2));
171 assert_eq!(parse_bytes("1.50MiB").unwrap(), 3 * 1024_u64.pow(2) / 2);
172 assert_eq!(parse_bytes("1MB").unwrap(), 1000_u64.pow(2));
173 assert_eq!(parse_bytes("1.50MB").unwrap(), 3 * 1000_u64.pow(2) / 2);
174
175 assert_eq!(parse_bytes("1GiB").unwrap(), 1024_u64.pow(3));
176 assert_eq!(parse_bytes("1.50GiB").unwrap(), 3 * 1024_u64.pow(3) / 2);
177 assert_eq!(parse_bytes("1GB").unwrap(), 1000_u64.pow(3));
178 assert_eq!(parse_bytes("1.50GB").unwrap(), 3 * 1000_u64.pow(3) / 2);
179
180 assert_eq!(parse_bytes("1TiB").unwrap(), 1024_u64.pow(4));
181 assert_eq!(parse_bytes("1.50TiB").unwrap(), 3 * 1024_u64.pow(4) / 2);
182 assert_eq!(parse_bytes("1TB").unwrap(), 1000_u64.pow(4));
183 assert_eq!(parse_bytes("1.50TB").unwrap(), 3 * 1000_u64.pow(4) / 2);
184
185 assert_eq!(parse_bytes("1PiB").unwrap(), 1024_u64.pow(5));
186 assert_eq!(parse_bytes("1.50PiB").unwrap(), 3 * 1024_u64.pow(5) / 2);
187 assert_eq!(parse_bytes("1PB").unwrap(), 1000_u64.pow(5));
188 assert_eq!(parse_bytes("1.50PB").unwrap(), 3 * 1000_u64.pow(5) / 2);
189
190 assert_eq!(parse_bytes("1EiB").unwrap(), 1024_u64.pow(6));
191 assert_eq!(parse_bytes("1.50EiB").unwrap(), 3 * 1024_u64.pow(6) / 2);
192 assert_eq!(parse_bytes("1EB").unwrap(), 1000_u64.pow(6));
193 assert_eq!(parse_bytes("1.50EB").unwrap(), 3 * 1000_u64.pow(6) / 2);
194 }
195
196 #[test]
197 fn test_fail_parse_bytes() {
198 assert!(parse_bytes("1.xE").is_err());
199 assert!(parse_bytes("1.xEB").is_err());
200 assert!(parse_bytes("1.50FB").is_err());
201 assert!(parse_bytes("1LB ").is_err());
202 assert!(parse_bytes(" 1.50Li").is_err());
203 assert!(parse_bytes(" MiB ").is_err());
204 assert!(parse_bytes("MB").is_err());
205 }
206}