1use std::num::{NonZeroU32, NonZeroU64};
8
9use crate::{Result, UtilsError};
10
11pub fn non_zero_u32(value: u32) -> NonZeroU32 {
16 NonZeroU32::new(value).unwrap_or(NonZeroU32::MIN)
17}
18
19pub fn non_zero_u64(value: u64) -> NonZeroU64 {
23 NonZeroU64::new(value).unwrap_or(NonZeroU64::MIN)
24}
25
26pub fn bytes_to_usize(bytes: i64, value_name: &str) -> Result<usize> {
28 i64_to_usize(bytes, value_name)
29}
30
31pub fn i32_to_usize(value: i32, value_name: &str) -> Result<usize> {
33 usize::try_from(value).map_err(|_| {
34 UtilsError::numeric_conversion(format!("{value_name} cannot be negative: {value}"))
35 })
36}
37
38pub fn i64_to_i32(value: i64, value_name: &str) -> Result<i32> {
40 i32::try_from(value).map_err(|_| {
41 UtilsError::numeric_conversion(format!("{value_name} is outside i32 range: {value}"))
42 })
43}
44
45pub fn i64_to_usize(value: i64, value_name: &str) -> Result<usize> {
47 usize::try_from(value).map_err(|_| {
48 UtilsError::numeric_conversion(format!(
49 "{value_name} exceeds platform usize range or is negative: {value}"
50 ))
51 })
52}
53
54pub fn i64_to_u64(value: i64, value_name: &str) -> Result<u64> {
56 u64::try_from(value).map_err(|_| {
57 UtilsError::numeric_conversion(format!("{value_name} cannot be negative: {value}"))
58 })
59}
60
61pub fn u128_to_u64(value: u128, value_name: &str) -> Result<u64> {
63 u64::try_from(value).map_err(|_| {
64 UtilsError::numeric_conversion(format!("{value_name} exceeds u64 range: {value}"))
65 })
66}
67
68pub fn u128_to_u64_saturating(value: u128) -> u64 {
70 u64::try_from(value).unwrap_or(u64::MAX)
71}
72
73pub fn f64_seconds_to_u64_millis(seconds: f64, value_name: &str) -> Result<u64> {
75 if !seconds.is_finite() {
76 return Err(UtilsError::invalid_value(format!(
77 "{value_name} must be finite: {seconds}"
78 )));
79 }
80 if seconds < 0.0 {
81 return Err(UtilsError::invalid_value(format!(
82 "{value_name} cannot be negative: {seconds}"
83 )));
84 }
85
86 let duration = std::time::Duration::try_from_secs_f64(seconds).map_err(|_| {
87 UtilsError::invalid_value(format!("{value_name} exceeds duration range: {seconds}"))
88 })?;
89 let rounded_duration = duration
90 .checked_add(std::time::Duration::from_micros(500))
91 .ok_or_else(|| {
92 UtilsError::invalid_value(format!("{value_name} exceeds duration range: {seconds}"))
93 })?;
94
95 u128_to_u64(rounded_duration.as_millis(), value_name)
96}
97
98pub fn u32_to_usize(value: u32, value_name: &str) -> Result<usize> {
100 usize::try_from(value).map_err(|_| {
101 UtilsError::numeric_conversion(format!(
102 "{value_name} exceeds platform usize range: {value}"
103 ))
104 })
105}
106
107pub fn u32_to_i64(value: u32) -> i64 {
111 i64::from(value)
112}
113
114pub fn u32_to_i32(value: u32, value_name: &str) -> Result<i32> {
116 i32::try_from(value).map_err(|_| {
117 UtilsError::numeric_conversion(format!("{value_name} exceeds i32 range: {value}"))
118 })
119}
120
121pub fn u64_to_i64(value: u64, value_name: &str) -> Result<i64> {
123 i64::try_from(value).map_err(|_| {
124 UtilsError::numeric_conversion(format!("{value_name} exceeds i64 range: {value}"))
125 })
126}
127
128pub fn u64_to_usize(value: u64, value_name: &str) -> Result<usize> {
130 usize::try_from(value).map_err(|_| {
131 UtilsError::numeric_conversion(format!(
132 "{value_name} exceeds platform usize range: {value}"
133 ))
134 })
135}
136
137pub fn usize_to_i32(value: usize, value_name: &str) -> Result<i32> {
139 i32::try_from(value).map_err(|_| {
140 UtilsError::numeric_conversion(format!("{value_name} exceeds i32 range: {value}"))
141 })
142}
143
144pub fn usize_to_i64(value: usize, value_name: &str) -> Result<i64> {
149 i64::try_from(value).map_err(|_| {
150 UtilsError::numeric_conversion(format!("{value_name} exceeds i64 range: {value}"))
151 })
152}
153
154pub fn usize_to_u32(value: usize, value_name: &str) -> Result<u32> {
156 u32::try_from(value).map_err(|_| {
157 UtilsError::numeric_conversion(format!("{value_name} exceeds u32 range: {value}"))
158 })
159}
160
161pub fn usize_to_u64(value: usize, value_name: &str) -> Result<u64> {
163 u64::try_from(value).map_err(|_| {
164 UtilsError::numeric_conversion(format!("{value_name} exceeds u64 range: {value}"))
165 })
166}
167
168pub fn calc_total_chunks(total_size: i64, chunk_size: i64, context: &str) -> Result<i32> {
170 if total_size < 0 {
171 return Err(UtilsError::invalid_value(format!(
172 "{context} total_size cannot be negative: {total_size}"
173 )));
174 }
175 if chunk_size <= 0 {
176 return Err(UtilsError::invalid_value(format!(
177 "{context} chunk_size must be positive, got {chunk_size}"
178 )));
179 }
180
181 let adjusted = total_size.checked_add(chunk_size - 1).ok_or_else(|| {
182 UtilsError::invalid_value(format!("{context} total_size is too large: {total_size}"))
183 })?;
184 let chunks = adjusted / chunk_size;
185
186 i32::try_from(chunks).map_err(|_| {
187 UtilsError::invalid_value(format!("{context} requires too many chunks: {chunks}"))
188 })
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 #[test]
196 fn bytes_to_usize_accepts_positive_values() {
197 assert_eq!(bytes_to_usize(5_242_880, "chunk_size").unwrap(), 5_242_880);
198 }
199
200 #[test]
201 fn non_zero_helpers_preserve_positive_values() {
202 assert_eq!(non_zero_u32(7).get(), 7);
203 assert_eq!(non_zero_u64(9).get(), 9);
204 }
205
206 #[test]
207 fn non_zero_helpers_fallback_to_min_for_zero() {
208 assert_eq!(non_zero_u32(0), NonZeroU32::MIN);
209 assert_eq!(non_zero_u64(0), NonZeroU64::MIN);
210 }
211
212 #[test]
213 fn bytes_to_usize_rejects_negative_values() {
214 let err = bytes_to_usize(-1, "chunk_size").unwrap_err();
215 assert!(matches!(err, UtilsError::NumericConversion(_)));
216 }
217
218 #[test]
219 fn i32_to_usize_rejects_negative_values() {
220 let err = i32_to_usize(-1, "total_chunks").unwrap_err();
221 assert!(matches!(err, UtilsError::NumericConversion(_)));
222 }
223
224 #[test]
225 fn i64_to_i32_accepts_bounds_and_rejects_overflow() {
226 assert_eq!(i64_to_i32(i64::from(i32::MIN), "value").unwrap(), i32::MIN);
227 assert_eq!(i64_to_i32(i64::from(i32::MAX), "value").unwrap(), i32::MAX);
228
229 let positive = i64_to_i32(i64::from(i32::MAX) + 1, "value").unwrap_err();
230 assert!(matches!(positive, UtilsError::NumericConversion(_)));
231
232 let negative = i64_to_i32(i64::from(i32::MIN) - 1, "value").unwrap_err();
233 assert!(matches!(negative, UtilsError::NumericConversion(_)));
234 }
235
236 #[test]
237 fn i64_to_usize_accepts_zero_and_rejects_negative_values() {
238 assert_eq!(i64_to_usize(0, "offset").unwrap(), 0);
239 assert_eq!(i64_to_usize(42, "offset").unwrap(), 42);
240
241 let err = i64_to_usize(-1, "offset").unwrap_err();
242 assert!(matches!(err, UtilsError::NumericConversion(_)));
243 }
244
245 #[test]
246 fn i64_to_u64_accepts_positive_values() {
247 assert_eq!(i64_to_u64(42, "content_length").unwrap(), 42);
248 }
249
250 #[test]
251 fn i64_to_u64_rejects_negative_values() {
252 let err = i64_to_u64(-1, "content_length").unwrap_err();
253 assert!(matches!(err, UtilsError::NumericConversion(_)));
254 }
255
256 #[test]
257 fn u128_to_u64_accepts_bounds_and_rejects_overflow() {
258 assert_eq!(u128_to_u64(0, "size").unwrap(), 0);
259 assert_eq!(u128_to_u64(u128::from(u64::MAX), "size").unwrap(), u64::MAX);
260
261 let err = u128_to_u64(u128::from(u64::MAX) + 1, "size").unwrap_err();
262 assert!(matches!(err, UtilsError::NumericConversion(_)));
263 }
264
265 #[test]
266 fn u128_to_u64_saturating_clamps_overflow() {
267 assert_eq!(u128_to_u64_saturating(0), 0);
268 assert_eq!(u128_to_u64_saturating(u128::from(u64::MAX)), u64::MAX);
269 assert_eq!(u128_to_u64_saturating(u128::from(u64::MAX) + 1), u64::MAX);
270 }
271
272 #[test]
273 fn f64_seconds_to_u64_millis_rounds_to_nearest_millisecond() {
274 assert_eq!(f64_seconds_to_u64_millis(1.2344, "duration").unwrap(), 1234);
275 assert_eq!(f64_seconds_to_u64_millis(1.2345, "duration").unwrap(), 1235);
276 assert_eq!(f64_seconds_to_u64_millis(0.0004, "duration").unwrap(), 0);
277 assert_eq!(f64_seconds_to_u64_millis(0.0005, "duration").unwrap(), 1);
278 }
279
280 #[test]
281 fn f64_seconds_to_u64_millis_accepts_zero() {
282 assert_eq!(f64_seconds_to_u64_millis(0.0, "duration").unwrap(), 0);
283 }
284
285 #[test]
286 fn f64_seconds_to_u64_millis_rejects_invalid_values() {
287 let negative = f64_seconds_to_u64_millis(-1.0, "duration").unwrap_err();
288 assert!(matches!(negative, UtilsError::InvalidValue(_)));
289
290 let nan = f64_seconds_to_u64_millis(f64::NAN, "duration").unwrap_err();
291 assert!(matches!(nan, UtilsError::InvalidValue(_)));
292
293 let infinity = f64_seconds_to_u64_millis(f64::INFINITY, "duration").unwrap_err();
294 assert!(matches!(infinity, UtilsError::InvalidValue(_)));
295 }
296
297 #[test]
298 fn f64_seconds_to_u64_millis_rejects_u64_millis_overflow() {
299 let overflow_seconds = "18446744073709552".parse::<f64>().unwrap();
300 let err = f64_seconds_to_u64_millis(overflow_seconds, "duration").unwrap_err();
301 assert!(matches!(err, UtilsError::NumericConversion(_)));
302 }
303
304 #[test]
305 fn u32_conversions_are_lossless_on_supported_targets() {
306 assert_eq!(u32_to_i32(0, "value").unwrap(), 0);
307 assert_eq!(u32_to_i32(i32::MAX as u32, "value").unwrap(), i32::MAX);
308 assert_eq!(u32_to_i64(u32::MAX), i64::from(u32::MAX));
309 assert_eq!(u32_to_usize(0, "value").unwrap(), 0);
310
311 #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))]
312 assert_eq!(u32_to_usize(u32::MAX, "value").unwrap(), u32::MAX as usize);
313 }
314
315 #[test]
316 fn u32_to_i32_rejects_overflow() {
317 let overflow = u32::try_from(i32::MAX)
318 .unwrap_or(u32::MAX)
319 .saturating_add(1);
320 let err = u32_to_i32(overflow, "value").unwrap_err();
321 assert!(matches!(err, UtilsError::NumericConversion(_)));
322 }
323
324 #[test]
325 fn usize_to_i32_rejects_overflow() {
326 let overflow = usize::try_from(i32::MAX)
327 .unwrap_or(usize::MAX)
328 .saturating_add(1);
329 let err = usize_to_i32(overflow, "uploaded_part_count").unwrap_err();
330 assert!(matches!(err, UtilsError::NumericConversion(_)));
331 }
332
333 #[test]
334 fn usize_to_u32_accepts_bounds_and_rejects_overflow() {
335 assert_eq!(usize_to_u32(0, "part_count").unwrap(), 0);
336
337 let max = usize::try_from(u32::MAX).unwrap_or(usize::MAX);
338 assert_eq!(usize_to_u32(max, "part_count").unwrap(), u32::MAX);
339
340 if let Some(overflow) = max.checked_add(1) {
341 let err = usize_to_u32(overflow, "part_count").unwrap_err();
342 assert!(matches!(err, UtilsError::NumericConversion(_)));
343 }
344 }
345
346 #[test]
347 fn usize_to_i64_accepts_small_values() {
348 assert_eq!(usize_to_i64(1024, "body_len").unwrap(), 1024);
349 }
350
351 #[test]
352 fn usize_to_u64_accepts_common_values() {
353 assert_eq!(usize_to_u64(0, "test").unwrap(), 0);
354 #[cfg(target_pointer_width = "64")]
355 assert_eq!(usize_to_u64(usize::MAX, "test").unwrap(), u64::MAX);
356 }
357
358 #[test]
359 fn u64_to_i64_accepts_within_i64_range() {
360 assert_eq!(u64_to_i64(0, "test").unwrap(), 0);
361 let max_i64_as_u64 = u64::try_from(i64::MAX).unwrap_or(u64::MAX);
362 assert_eq!(u64_to_i64(max_i64_as_u64, "test").unwrap(), i64::MAX);
363 }
364
365 #[test]
366 fn u64_to_i64_rejects_overflow() {
367 let overflow = u64::try_from(i64::MAX)
368 .unwrap_or(u64::MAX)
369 .saturating_add(1);
370 let err = u64_to_i64(overflow, "test").unwrap_err();
371 assert!(matches!(err, UtilsError::NumericConversion(_)));
372 }
373
374 #[test]
375 fn u64_to_usize_accepts_within_platform_range() {
376 assert_eq!(u64_to_usize(0, "test").unwrap(), 0);
377 #[cfg(target_pointer_width = "64")]
378 assert_eq!(u64_to_usize(u64::MAX, "test").unwrap(), usize::MAX);
379 }
381
382 #[test]
383 #[cfg(target_pointer_width = "32")]
384 fn u64_to_usize_rejects_overflow() {
385 let err = u64_to_usize(u64::MAX, "cursor_value").unwrap_err();
387 assert!(matches!(err, UtilsError::NumericConversion(_)));
388 }
389
390 #[test]
391 fn calc_total_chunks_rounds_up() {
392 assert_eq!(
393 calc_total_chunks(10_485_761, 5_242_880, "multipart upload").unwrap(),
394 3
395 );
396 }
397
398 #[test]
399 fn calc_total_chunks_handles_exact_division() {
400 assert_eq!(
401 calc_total_chunks(10_485_760, 5_242_880, "multipart upload").unwrap(),
402 2
403 );
404 }
405
406 #[test]
407 fn calc_total_chunks_allows_zero_size() {
408 assert_eq!(calc_total_chunks(0, 5, "multipart upload").unwrap(), 0);
409 }
410
411 #[test]
412 fn calc_total_chunks_rejects_negative_total_size() {
413 let err = calc_total_chunks(-1, 5, "multipart upload").unwrap_err();
414 assert!(matches!(err, UtilsError::InvalidValue(_)));
415 }
416
417 #[test]
418 fn calc_total_chunks_rejects_non_positive_chunk_size() {
419 let err = calc_total_chunks(10, 0, "multipart upload").unwrap_err();
420 assert!(matches!(err, UtilsError::InvalidValue(_)));
421 }
422
423 #[test]
424 fn calc_total_chunks_rejects_i32_overflow() {
425 let overflow_total_size = (i64::from(i32::MAX) + 1) * 5;
426 let err = calc_total_chunks(overflow_total_size, 1, "multipart upload").unwrap_err();
427 assert!(matches!(err, UtilsError::InvalidValue(_)));
428 }
429}