deserialize_file_size/
lib.rs

1//! A serde helper function for deserializing file size input
2//! flexibly and robustly.
3//!
4//! Accepts either:
5//!
6//! 1) a "human" size string, e.g. "1k", "5mb", "12GiB", etc.
7//! 2) an integer number of bytes
8//!
9//! # Examples
10//!
11//! ```
12//! use serde::Deserialize;
13//! use deserialize_file_size::deserialize_file_size;
14//!
15//! #[derive(Deserialize, Debug, PartialEq)]
16//! struct FileSize {
17//!     #[serde(deserialize_with = "deserialize_file_size")]
18//!     sz: usize,
19//! }
20//!
21//! let size_str = r#"{"sz": "42mb"}"#;
22//! assert_eq!(
23//!     serde_json::from_str::<FileSize>(size_str).unwrap(),
24//!     FileSize { sz: 1024 * 1024 * 42 },
25//! );
26//!
27//! let int_bytes = r#"{"sz": 4096}"#;
28//! assert_eq!(
29//!     serde_json::from_str::<FileSize>(int_bytes).unwrap(),
30//!     FileSize { sz: 4096 },
31//! );
32//! ```
33
34#![allow(clippy::single_char_pattern)] // annoying, who the f cares "a" vs 'a'
35
36use std::fmt;
37use serde::de::{self, Visitor};
38
39/// returns size in bytes if parsing is successful. note: units "k"/"kb", "m"/"mb",
40/// and "g"/"gb" are converted to KiB, MiB, and GiB respectively. there are no 1000-based
41/// file sizes as far as this implementation is concerned, 1024 is file size god.
42pub fn parse_file_size(s: &str) -> Option<usize> {
43    let mut s = s.trim().to_string();
44    s[..].make_ascii_lowercase();
45    let s = if s.contains("ib") {
46        s
47    } else {
48        s.replace("kb", "k")        // first truncate kb/mb/gb -> k/m/g
49            .replace("mb", "m")
50            .replace("gb", "g")
51            .replace("k", "kib")    // then transform k/m/g -> kib/mib/gib
52            .replace("m", "mib")
53            .replace("g", "gib")
54    };
55
56    let bytes: u128 = byte_unit::Byte::from_str(&s)
57        .ok()?
58        .get_bytes();
59
60    usize::try_from(bytes).ok()
61}
62
63/// A serde "deserialize_with" helper function for parsing a `usize` field 
64/// flexibly and robustly.
65///
66/// Accepts input that is either:
67///
68/// 1) a "human" size string, e.g. "10k", "42mb", "7GiB", etc.
69/// 2) an integer number of bytes
70///
71/// To make the point explicit, either `String`/`&str` or integer
72/// (`visit_u64`-compatible) input is accepted.
73///
74/// # Examples
75///
76/// ```
77/// use serde::Deserialize;
78/// use deserialize_file_size::deserialize_file_size;
79///
80/// #[derive(Deserialize, Debug, PartialEq)]
81/// struct FileSize {
82///     #[serde(deserialize_with = "deserialize_file_size")]
83///     sz: usize,
84/// }
85///
86/// let size_str = r#"{"sz": "42mb"}"#;
87/// assert_eq!(
88///     serde_json::from_str::<FileSize>(size_str).unwrap(),
89///     FileSize { sz: 1024 * 1024 * 42 },
90/// );
91///
92/// let int_bytes = r#"{"sz": 4096}"#;
93/// assert_eq!(
94///     serde_json::from_str::<FileSize>(int_bytes).unwrap(),
95///     FileSize { sz: 4096 },
96/// );
97/// ```
98pub fn deserialize_file_size<'de, D>(deserializer: D) -> Result<usize, D::Error>
99    where D: serde::de::Deserializer<'de>
100{
101    struct SizeStringOrUsize;
102
103    impl<'de> Visitor<'de> for SizeStringOrUsize {
104        type Value = usize;
105
106        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
107            formatter.write_str("file size string or integer number of bytes")
108        }
109
110        fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
111        where
112            E: de::Error,
113        {
114            match parse_file_size(value) {
115                Some(size) => Ok(size),
116                None => {
117                    Err(serde::de::Error::custom(format!(
118                        "parsing file size input '{}' failed; expected number followed \
119                         by human size abbreviation (e.g. 10k/5mb/15GiB) or integer number \
120                         of bytes",
121                         value,
122                    )))
123                }
124            }
125        }
126
127        fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
128            where E: de::Error,
129        {
130            Ok(value as usize)
131        }
132    }
133
134    deserializer.deserialize_any(SizeStringOrUsize)
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn check_file_size_parser_against_several_inputs() {
143        assert_eq!(parse_file_size("1k")    , Some(1024));
144        assert_eq!(parse_file_size("1kib")  , Some(1024));
145        assert_eq!(parse_file_size("1K")    , Some(1024));
146        assert_eq!(parse_file_size("1Kb")   , Some(1024));
147        assert_eq!(parse_file_size("1kb")   , Some(1024));
148        assert_eq!(parse_file_size("1 kb")  , Some(1024));
149        assert_eq!(parse_file_size("1 k")   , Some(1024));
150        assert_eq!(parse_file_size("1 kib") , Some(1024));
151
152        assert_eq!(parse_file_size("1m")    , Some(1024 * 1024));
153        assert_eq!(parse_file_size("1mib")  , Some(1024 * 1024));
154        assert_eq!(parse_file_size("1M")    , Some(1024 * 1024));
155        assert_eq!(parse_file_size("1Mb")   , Some(1024 * 1024));
156        assert_eq!(parse_file_size("1MB")   , Some(1024 * 1024));
157        assert_eq!(parse_file_size("1mb")   , Some(1024 * 1024));
158        assert_eq!(parse_file_size("1 MB")  , Some(1024 * 1024));
159
160        assert_eq!(parse_file_size("1g")    , Some(1024 * 1024 * 1024));
161        assert_eq!(parse_file_size("1gib")  , Some(1024 * 1024 * 1024));
162        assert_eq!(parse_file_size("1G")    , Some(1024 * 1024 * 1024));
163        assert_eq!(parse_file_size("1Gb")   , Some(1024 * 1024 * 1024));
164        assert_eq!(parse_file_size("1gb")   , Some(1024 * 1024 * 1024));
165        assert_eq!(parse_file_size("1 gb")  , Some(1024 * 1024 * 1024));
166        assert_eq!(parse_file_size("1 g")   , Some(1024 * 1024 * 1024));
167        assert_eq!(parse_file_size("1 Gib") , Some(1024 * 1024 * 1024));
168
169        assert_eq!(parse_file_size("48G") , Some( 48 * 1024 * 1024 * 1024));
170        assert_eq!(parse_file_size("96G") , Some( 96 * 1024 * 1024 * 1024));
171        assert_eq!(parse_file_size("2G")  , Some(  2 * 1024 * 1024 * 1024));
172        assert_eq!(parse_file_size("128G"), Some(128 * 1024 * 1024 * 1024));
173    }
174
175    #[test]
176    fn check_deserialize_file_size() {
177        #[derive(serde::Deserialize)]
178        struct A {
179            #[serde(deserialize_with = "deserialize_file_size")]
180            sz: usize,
181        }
182
183        assert_eq!(
184            serde_json::from_str::<A>(r#"{"sz":"1mb"}"#).unwrap().sz,
185            1024 * 1024,
186        );
187        assert_eq!(
188            serde_json::from_str::<A>(r#"{"sz":4096}"#).unwrap().sz,
189            4096,
190        );
191    }
192}