convert_byte_size_string/
lib.rs1use std::convert::TryInto;
2
3#[cfg(test)]
4mod tests {
5 use super::{
6 convert_to_bytes, convert_to_bytes_base_10, convert_to_bytes_base_2, ConversionError,
7 };
8 #[test]
9 fn test_kb_uppercase() {
10 assert_eq!(1000_u128, convert_to_bytes("1 KB").unwrap());
11 }
12
13 #[test]
14 fn test_kb_lowercase() {
15 assert_eq!(1000_u128, convert_to_bytes("1 kb").unwrap());
16 }
17
18 #[test]
19 fn test_kb_mixedcase() {
20 assert_eq!(1000_u128, convert_to_bytes("1 kB").unwrap());
21 }
22
23 #[test]
24 fn test_kib() {
25 assert_eq!(1024_u128, convert_to_bytes("1 KiB").unwrap());
26 }
27
28 #[test]
29 fn test_mb() {
30 assert_eq!(1_000_000_u128, convert_to_bytes("1 MB").unwrap());
31 }
32
33 #[test]
34 fn test_gb() {
35 assert_eq!(1_000_000_000_u128, convert_to_bytes("1 GB").unwrap());
36 }
37
38 #[test]
39 fn test_tb() {
40 assert_eq!(1_000_000_000_000_u128, convert_to_bytes("1 TB").unwrap());
41 }
42
43 #[test]
44 fn test_pb() {
45 assert_eq!(
46 1_000_000_000_000_000_u128,
47 convert_to_bytes("1 PB").unwrap()
48 );
49 }
50
51 #[test]
52 fn test_eb() {
53 assert_eq!(
54 1_000_000_000_000_000_000_u128,
55 convert_to_bytes("1 EB").unwrap()
56 );
57 }
58
59 #[test]
60 fn test_zb() {
61 assert_eq!(
62 1_000_000_000_000_000_000_000_u128,
63 convert_to_bytes("1 ZB").unwrap()
64 );
65 }
66
67 #[test]
68 fn test_yb() {
69 assert_eq!(
70 1_000_000_000_000_000_000_000_000_u128,
71 convert_to_bytes("1 YB").unwrap()
72 );
73 }
74
75 #[test]
76 fn test_decimal() {
77 assert_eq!(
78 9_108_079_886_394_091_110_u128,
79 convert_to_bytes("7.9 EiB").unwrap()
80 );
81 }
82
83 #[test]
84 fn test_long_decimal() {
85 assert_eq!(1_530_000, convert_to_bytes("1.53 MB").unwrap());
86 }
87
88 #[test]
89 fn test_no_space() {
90 assert_eq!(1024, convert_to_bytes("1KiB").unwrap());
91 }
92
93 #[test]
94 fn test_decimal_no_space() {
95 assert_eq!(1_530_000, convert_to_bytes("1.53MB").unwrap());
96 }
97
98 #[test]
99 fn test_forced_bases() {
100 assert_eq!(1000, convert_to_bytes_base_10("1KiB").unwrap());
101 assert_eq!(1024, convert_to_bytes_base_2("1KB").unwrap());
102 }
103
104 #[test]
105 fn test_too_large() {
106 match convert_to_bytes("281474976710657 YiB") {
107 Err(ConversionError::TooLarge) => return,
108 _ => panic!("Did not get a TooLarge error"),
109 }
110 }
111
112 #[test]
113 fn test_invalid() {
114 match convert_to_bytes("invalid input") {
115 Err(ConversionError::InputInvalid(_)) => return,
116 _ => panic!("Did not get an InputInvalid error"),
117 }
118 }
119
120 #[test]
121 fn test_invalid_units() {
122 match convert_to_bytes("1 bad unit") {
123 Err(ConversionError::InputInvalid(_)) => (),
124 _ => panic!("Did not get an InputInvalid error"),
125 }
126 match convert_to_bytes("1 kilobadunit") {
127 Err(ConversionError::InputInvalid(_)) => (),
128 _ => panic!("Did not get an InputInvalid error"),
129 }
130 match convert_to_bytes("1kbu") {
131 Err(ConversionError::InputInvalid(_)) => (return),
132 _ => panic!("Did not get an InputInvalid error"),
133 }
134 }
135}
136
137#[derive(Debug)]
139pub enum ConversionError {
140 InputInvalid(String),
142 TooLarge,
144}
145
146enum ParsingNumber {
148 Int(u128),
149 Float((u128, u128)),
150}
151
152enum ForceBase {
154 Base2,
155 Base10,
156 Implied,
157}
158
159pub fn convert_to_bytes(string: &str) -> Result<u128, ConversionError> {
179 convert_to_bytes_with_base(string, ForceBase::Implied)
180}
181
182pub fn convert_to_bytes_base_10(string: &str) -> Result<u128, ConversionError> {
184 convert_to_bytes_with_base(string, ForceBase::Base10)
185}
186
187pub fn convert_to_bytes_base_2(string: &str) -> Result<u128, ConversionError> {
189 convert_to_bytes_with_base(string, ForceBase::Base2)
190}
191
192fn convert_to_bytes_with_base(string: &str, base: ForceBase) -> Result<u128, ConversionError> {
194 let lowercase = string.to_lowercase();
195 let mut splits: Vec<&str> = lowercase.trim().split_whitespace().collect();
196 if splits.len() < 2 {
197 splits.clear();
198 let (index, _) = match lowercase
199 .trim()
200 .match_indices(|c: char| {
201 c == 'k'
202 || c == 'm'
203 || c == 'g'
204 || c == 't'
205 || c == 'p'
206 || c == 'e'
207 || c == 'z'
208 || c == 'y'
209 })
210 .next()
211 {
212 Some(val) => val,
213 None => {
214 return Err(ConversionError::InputInvalid(String::from(
215 "Did not find two parts in string",
216 )));
217 }
218 };
219
220 splits.push(&lowercase[..index]);
221 splits.push(&lowercase[index..]);
222 }
223
224 let mantissa: ParsingNumber;
225 match splits[0].parse::<u128>() {
226 Ok(n) => mantissa = ParsingNumber::Int(n),
227 Err(_) => {
228 let float_splits: Vec<&str> = splits[0].split('.').collect();
229 if float_splits.len() != 2 {
230 return Err(ConversionError::InputInvalid(format!(
231 "Could not parse '{}' into an i128 or an f64",
232 splits[0]
233 )));
234 }
235
236 let whole = match float_splits[0].parse::<u128>() {
237 Ok(val) => val,
238 Err(_) => {
239 return Err(ConversionError::InputInvalid(format!(
240 "Could not parse '{}' into an i128 or an f64",
241 splits[0]
242 )));
243 }
244 };
245 let fraction = match float_splits[1].parse::<u128>() {
246 Ok(val) => val,
247 Err(_) => {
248 return Err(ConversionError::InputInvalid(format!(
249 "Could not parse '{}' into an i128 or an f64",
250 splits[0]
251 )));
252 }
253 };
254 mantissa = ParsingNumber::Float((whole, fraction));
255 }
256 }
257
258 let exponent = parse_exponent(splits[1], base)?;
259
260 match mantissa {
261 ParsingNumber::Int(m) => match m.checked_mul(exponent) {
262 Some(val) => Ok(val),
263 None => Err(ConversionError::TooLarge),
264 },
265 ParsingNumber::Float(m) => {
266 if let Some(whole) = m.0.checked_mul(exponent) {
267 let fraction_digits: u32 = match m.1.to_string().len().try_into() {
268 Ok(val) => val,
269 Err(_) => {
270 return Err(ConversionError::InputInvalid(String::from(
271 "Decimal portion of input too long",
272 )));
273 }
274 };
275 if let Some(fraction) = m.1.checked_mul(exponent) {
276 if let Some(fraction) = fraction.checked_div(10u128.pow(fraction_digits)) {
277 if let Some(val) = whole.checked_add(fraction) {
278 return Ok(val);
279 }
280 }
281 }
282 }
283 Err(ConversionError::TooLarge)
284 }
285 }
286}
287
288fn parse_exponent(string: &str, base: ForceBase) -> Result<u128, ConversionError> {
290 if !string.is_ascii() {
291 return Err(ConversionError::InputInvalid(format!(
292 "Could not parse '{}' because it contains invalid characters",
293 string
294 )));
295 }
296
297 let chars: Vec<char> = string.to_lowercase().chars().collect();
298 if chars.len() < 2 {
299 return Err(ConversionError::InputInvalid(String::from(
300 "Unit not long enough",
301 )));
302 }
303
304 let base_1000: u128 = match base {
305 ForceBase::Implied => match chars[1] {
306 'b' if chars.len() == 2 => 1000,
307 'i' if chars.len() > 2 && chars[2] == 'b' => 1024,
308 _ if chars[2] == 'b' => {
309 return Err(ConversionError::InputInvalid(format!(
310 "Invalid character in unit: {}",
311 chars[1]
312 )));
313 }
314 _ => {
315 return Err(ConversionError::InputInvalid(format!(
316 "Invalid unit: {}",
317 string
318 )));
319 }
320 },
321 ForceBase::Base10 => 1000,
322 ForceBase::Base2 => 1024,
323 };
324
325 let exponent: u128 = match chars[0] {
326 'k' => base_1000,
327 'm' => base_1000 * base_1000,
328 'g' => base_1000 * base_1000 * base_1000,
329 't' => base_1000 * base_1000 * base_1000 * base_1000,
330 'p' => base_1000 * base_1000 * base_1000 * base_1000 * base_1000,
331 'e' => base_1000 * base_1000 * base_1000 * base_1000 * base_1000 * base_1000,
332 'z' => base_1000 * base_1000 * base_1000 * base_1000 * base_1000 * base_1000 * base_1000,
333 'y' => {
334 base_1000
335 * base_1000
336 * base_1000
337 * base_1000
338 * base_1000
339 * base_1000
340 * base_1000
341 * base_1000
342 }
343 _ => {
344 return Err(ConversionError::InputInvalid(format!(
345 "Invalid character in unit: {}",
346 chars[0]
347 )));
348 }
349 };
350
351 Ok(exponent)
352}