Skip to main content

microsandbox_utils/
size.rs

1//! Byte-size types and conversion helpers.
2//!
3//! Provides [`ByteSize`], [`Bytes`], and [`Mebibytes`] for type-safe size
4//! specification across the project. The [`SizeExt`] trait adds `.bytes()`,
5//! `.kib()`, `.mib()`, and `.gib()` helpers to integer literals.
6//!
7//! ```ignore
8//! use microsandbox_utils::size::{SizeExt, Mebibytes};
9//!
10//! // All equivalent — 512 MiB:
11//! let a: Mebibytes = 512.into();    // bare integer
12//! let b: Mebibytes = 512.mib().into(); // explicit unit
13//!
14//! // Cross-unit conversion:
15//! let c: Mebibytes = 1.gib().into();  // 1 GiB → 1024 MiB
16//! ```
17
18//--------------------------------------------------------------------------------------------------
19// Types
20//--------------------------------------------------------------------------------------------------
21
22/// A byte-size value returned by [`SizeExt`] helpers.
23///
24/// Acts as the universal intermediate type that converts [`Into`] both
25/// [`Bytes`] and [`Mebibytes`].
26#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
27pub struct ByteSize(u64);
28
29/// A size measured in bytes.
30///
31/// Accepted by APIs that operate at byte-level precision (e.g. filesystem
32/// capacity, rlimit values).
33///
34/// **Bare integer path:** `u64` converts directly via [`From`].
35/// **Helper path:** any [`ByteSize`] (from `.kib()`, `.mib()`, `.gib()`)
36/// converts via [`From`].
37#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
38pub struct Bytes(u64);
39
40/// A size measured in mebibytes (MiB).
41///
42/// Accepted by APIs that operate at MiB-level precision (e.g. sandbox
43/// memory, volume quota, tmpfs size).
44///
45/// **Bare integer path:** `u32` converts directly via [`From`].
46/// **Helper path:** any [`ByteSize`] (from `.kib()`, `.mib()`, `.gib()`)
47/// converts via [`From`] (truncates to whole MiB).
48#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
49pub struct Mebibytes(u32);
50
51/// Helper trait for readable byte sizes.
52///
53/// Implemented for common integer types so that literals like `512.mib()`
54/// or `1.gib()` return a [`ByteSize`] that converts into either [`Bytes`]
55/// or [`Mebibytes`].
56pub trait SizeExt {
57    /// Create a [`ByteSize`] representing this many bytes.
58    fn bytes(self) -> ByteSize;
59    /// Create a [`ByteSize`] representing this many kibibytes (×1024).
60    fn kib(self) -> ByteSize;
61    /// Create a [`ByteSize`] representing this many mebibytes (×1024²).
62    fn mib(self) -> ByteSize;
63    /// Create a [`ByteSize`] representing this many gibibytes (×1024³).
64    fn gib(self) -> ByteSize;
65}
66
67//--------------------------------------------------------------------------------------------------
68// Methods
69//--------------------------------------------------------------------------------------------------
70
71impl ByteSize {
72    /// Get the raw byte count.
73    pub fn as_bytes(self) -> u64 {
74        self.0
75    }
76
77    /// Get the value in whole mebibytes (truncates sub-MiB remainder).
78    pub fn as_mib(self) -> u32 {
79        (self.0 / (1024 * 1024)) as u32
80    }
81}
82
83impl Bytes {
84    /// Get the raw byte count.
85    pub fn as_u64(self) -> u64 {
86        self.0
87    }
88}
89
90impl Mebibytes {
91    /// Get the MiB count.
92    pub fn as_u32(self) -> u32 {
93        self.0
94    }
95}
96
97//--------------------------------------------------------------------------------------------------
98// Trait Implementations
99//--------------------------------------------------------------------------------------------------
100
101// ByteSize → destination types.
102
103impl From<ByteSize> for Bytes {
104    fn from(bs: ByteSize) -> Self {
105        Self(bs.0)
106    }
107}
108
109impl From<ByteSize> for Mebibytes {
110    fn from(bs: ByteSize) -> Self {
111        Self((bs.0 / (1024 * 1024)) as u32)
112    }
113}
114
115// Bare integers → destination types.
116
117impl From<u64> for Bytes {
118    fn from(v: u64) -> Self {
119        Self(v)
120    }
121}
122
123impl From<u32> for Mebibytes {
124    fn from(v: u32) -> Self {
125        Self(v)
126    }
127}
128
129// SizeExt implementations for common integer types.
130
131macro_rules! impl_size_ext {
132    ($($t:ty),*) => {
133        $(
134            impl SizeExt for $t {
135                fn bytes(self) -> ByteSize { ByteSize(self as u64) }
136                fn kib(self) -> ByteSize { ByteSize(self as u64 * 1024) }
137                fn mib(self) -> ByteSize { ByteSize(self as u64 * 1024 * 1024) }
138                fn gib(self) -> ByteSize { ByteSize(self as u64 * 1024 * 1024 * 1024) }
139            }
140        )*
141    };
142}
143
144impl_size_ext!(u8, u16, u32, u64, usize, i32);
145
146//--------------------------------------------------------------------------------------------------
147// Tests
148//--------------------------------------------------------------------------------------------------
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn test_size_ext_helpers() {
156        assert_eq!(1u64.kib().as_bytes(), 1024);
157        assert_eq!(1u64.mib().as_bytes(), 1024 * 1024);
158        assert_eq!(1u64.gib().as_bytes(), 1024 * 1024 * 1024);
159        assert_eq!(512u64.mib().as_bytes(), 512 * 1024 * 1024);
160        assert_eq!(64i32.mib().as_bytes(), 64 * 1024 * 1024);
161    }
162
163    #[test]
164    fn test_bytesize_to_mebibytes() {
165        let mib: Mebibytes = 512.mib().into();
166        assert_eq!(mib.as_u32(), 512);
167
168        let mib: Mebibytes = 1.gib().into();
169        assert_eq!(mib.as_u32(), 1024);
170    }
171
172    #[test]
173    fn test_bytesize_to_bytes() {
174        let b: Bytes = 64.mib().into();
175        assert_eq!(b.as_u64(), 64 * 1024 * 1024);
176    }
177
178    #[test]
179    fn test_bare_u32_to_mebibytes() {
180        let mib: Mebibytes = 512u32.into();
181        assert_eq!(mib.as_u32(), 512);
182    }
183
184    #[test]
185    fn test_bare_u64_to_bytes() {
186        let b: Bytes = 4096u64.into();
187        assert_eq!(b.as_u64(), 4096);
188    }
189
190    #[test]
191    fn test_truncation() {
192        // 1.5 MiB → truncates to 1 MiB
193        let bs = ByteSize(1024 * 1024 + 512 * 1024);
194        let mib: Mebibytes = bs.into();
195        assert_eq!(mib.as_u32(), 1);
196    }
197
198    #[test]
199    fn test_bytes_helper() {
200        assert_eq!(4096.bytes().as_bytes(), 4096);
201    }
202}