Skip to main content

cfg_rs/value/
byte.rs

1use crate::{ConfigContext, FromValue};
2use std::convert::TryFrom;
3
4/// Byte size value in bytes. Use `u64` for platform independence.
5#[derive(Debug, Clone, Copy)]
6pub struct ByteSize(u64);
7
8impl std::ops::Deref for ByteSize {
9    type Target = u64;
10
11    fn deref(&self) -> &Self::Target {
12        &self.0
13    }
14}
15
16impl From<ByteSize> for u64 {
17    fn from(v: ByteSize) -> Self {
18        v.0
19    }
20}
21
22impl From<ByteSize> for usize {
23    fn from(v: ByteSize) -> Self {
24        usize::try_from(v.0).expect("ByteSize value doesn't fit usize")
25    }
26}
27
28#[inline]
29fn parse_byte_size_from_str(
30    context: &mut ConfigContext<'_>,
31    value: &str,
32) -> Result<ByteSize, crate::err::ConfigError> {
33    let v = value.trim();
34    if v.is_empty() {
35        return Err(context.parse_error(value));
36    }
37
38    let mut split = 0usize;
39    for (idx, c) in v.char_indices() {
40        if c.is_ascii_digit() {
41            split = idx + c.len_utf8();
42        } else {
43            break;
44        }
45    }
46    if split == 0 {
47        return Err(context.parse_error(value));
48    }
49
50    let num = v[..split]
51        .parse::<u64>()
52        .map_err(crate::err::ConfigError::from_cause)?;
53    let unit = v[split..].trim().to_ascii_lowercase();
54    let mul = match unit.as_str() {
55        "" | "b" => 1,
56        "kb" => 1024,
57        "mb" => 1024u64.pow(2),
58        "gb" => 1024u64.pow(3),
59        "tb" => 1024u64.pow(4),
60        "pb" => 1024u64.pow(5),
61        _ => return Err(context.parse_error(value)),
62    };
63    num.checked_mul(mul)
64        .map(ByteSize)
65        .ok_or_else(|| context.parse_error(value))
66}
67
68impl FromValue for ByteSize {
69    fn from_value(
70        context: &mut ConfigContext<'_>,
71        value: crate::ConfigValue<'_>,
72    ) -> Result<Self, crate::err::ConfigError> {
73        match value {
74            crate::ConfigValue::Str(v) => parse_byte_size_from_str(context, &v),
75            crate::ConfigValue::StrRef(v) => parse_byte_size_from_str(context, v),
76            crate::ConfigValue::Int(v) => Ok(ByteSize(
77                u64::try_from(v).map_err(crate::err::ConfigError::from_cause)?,
78            )),
79            _ => Err(context.type_mismatch::<Self>(&value)),
80        }
81    }
82}
83
84#[cfg(test)]
85#[cfg_attr(coverage_nightly, coverage(off))]
86mod test {
87    use super::*;
88    use crate::ConfigValue;
89    use crate::{Configuration, key::CacheString};
90
91    struct TestContext(Configuration, CacheString);
92
93    impl TestContext {
94        fn new() -> Self {
95            Self(Configuration::new(), CacheString::new())
96        }
97    }
98
99    #[test]
100    fn parse_byte_size_from_str_cases() {
101        let mut context = TestContext::new();
102
103        assert_eq!(
104            *parse_byte_size_from_str(&mut context.0.source.new_context(&mut context.1), "1")
105                .unwrap(),
106            1
107        );
108        assert_eq!(
109            *parse_byte_size_from_str(&mut context.0.source.new_context(&mut context.1), "1b")
110                .unwrap(),
111            1
112        );
113        assert_eq!(
114            *parse_byte_size_from_str(&mut context.0.source.new_context(&mut context.1), "2KB")
115                .unwrap(),
116            2 * 1024
117        );
118        assert_eq!(
119            *parse_byte_size_from_str(&mut context.0.source.new_context(&mut context.1), "3mb")
120                .unwrap(),
121            3 * 1024 * 1024
122        );
123        assert_eq!(
124            *parse_byte_size_from_str(&mut context.0.source.new_context(&mut context.1), "4Gb")
125                .unwrap(),
126            4 * 1024 * 1024 * 1024
127        );
128        assert_eq!(
129            *parse_byte_size_from_str(&mut context.0.source.new_context(&mut context.1), "1tb")
130                .unwrap(),
131            1024u64.pow(4)
132        );
133        assert_eq!(
134            *parse_byte_size_from_str(&mut context.0.source.new_context(&mut context.1), "1PB")
135                .unwrap(),
136            1024u64.pow(5)
137        );
138
139        assert!(
140            parse_byte_size_from_str(&mut context.0.source.new_context(&mut context.1), "abc")
141                .is_err()
142        );
143        assert!(
144            parse_byte_size_from_str(&mut context.0.source.new_context(&mut context.1), "1.5kb")
145                .is_err()
146        );
147        assert!(
148            parse_byte_size_from_str(&mut context.0.source.new_context(&mut context.1), "1zb")
149                .is_err()
150        );
151        assert!(
152            parse_byte_size_from_str(&mut context.0.source.new_context(&mut context.1), "   ")
153                .is_err()
154        );
155        assert!(
156            parse_byte_size_from_str(
157                &mut context.0.source.new_context(&mut context.1),
158                "18446744073709551616b"
159            )
160            .is_err()
161        );
162        assert!(
163            parse_byte_size_from_str(
164                &mut context.0.source.new_context(&mut context.1),
165                &format!("{}pb", u64::MAX)
166            )
167            .is_err()
168        );
169    }
170
171    #[test]
172    fn from_value_for_byte_size() {
173        let mut context = TestContext::new();
174
175        let v = <ByteSize as FromValue>::from_value(
176            &mut context.0.source.new_context(&mut context.1),
177            ConfigValue::Str("1kb".to_string()),
178        );
179        assert_eq!(*v.unwrap(), 1024);
180
181        let v = <ByteSize as FromValue>::from_value(
182            &mut context.0.source.new_context(&mut context.1),
183            ConfigValue::StrRef("2MB"),
184        );
185        assert_eq!(*v.unwrap(), 2 * 1024 * 1024);
186
187        let v = <ByteSize as FromValue>::from_value(
188            &mut context.0.source.new_context(&mut context.1),
189            ConfigValue::Int(7),
190        );
191        assert_eq!(*v.unwrap(), 7);
192
193        let v = <ByteSize as FromValue>::from_value(
194            &mut context.0.source.new_context(&mut context.1),
195            ConfigValue::Int(-1),
196        );
197        assert!(v.is_err());
198
199        let v = <ByteSize as FromValue>::from_value(
200            &mut context.0.source.new_context(&mut context.1),
201            ConfigValue::Float(1.0),
202        );
203        assert!(v.is_err());
204    }
205
206    #[test]
207    fn byte_size_accessors() {
208        let v = ByteSize(1024);
209        assert_eq!(*v, 1024);
210        let n: usize = v.into();
211        assert_eq!(n, 1024);
212        let n64: u64 = v.into();
213        assert_eq!(n64, 1024);
214    }
215}