1use crate::{ConfigContext, FromValue};
2use std::convert::TryFrom;
3
4#[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}