reifydb_value/
byte_size.rs1use std::{
5 fmt::{self, Display, Formatter},
6 ops::{Add, Mul},
7};
8
9use serde::{Deserialize, Serialize};
10
11const KIB: u64 = 1024;
12const MIB: u64 = 1024 * 1024;
13const GIB: u64 = 1024 * 1024 * 1024;
14
15#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
16#[repr(transparent)]
17pub struct ByteSize(u64);
18
19impl ByteSize {
20 pub const ZERO: Self = Self(0);
21
22 pub const fn from_bytes(bytes: u64) -> Self {
23 Self(bytes)
24 }
25
26 pub const fn from_kib(kib: u64) -> Self {
27 Self(kib * KIB)
28 }
29
30 pub const fn from_mib(mib: u64) -> Self {
31 Self(mib * MIB)
32 }
33
34 pub const fn from_gib(gib: u64) -> Self {
35 Self(gib * GIB)
36 }
37
38 pub const fn as_bytes(self) -> u64 {
39 self.0
40 }
41
42 pub const fn as_kib(self) -> u64 {
43 self.0 / KIB
44 }
45
46 pub const fn saturating_add(self, other: Self) -> Self {
47 Self(self.0.saturating_add(other.0))
48 }
49
50 pub const fn saturating_sub(self, other: Self) -> Self {
51 Self(self.0.saturating_sub(other.0))
52 }
53}
54
55impl Add for ByteSize {
56 type Output = Self;
57
58 fn add(self, rhs: Self) -> Self {
59 Self(self.0 + rhs.0)
60 }
61}
62
63impl Mul<u64> for ByteSize {
64 type Output = Self;
65
66 fn mul(self, rhs: u64) -> Self {
67 Self(self.0 * rhs)
68 }
69}
70
71impl Display for ByteSize {
72 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
73 let bytes = self.0;
74 if bytes >= GIB && bytes.is_multiple_of(GIB) {
75 write!(f, "{} GiB", bytes / GIB)
76 } else if bytes >= MIB && bytes.is_multiple_of(MIB) {
77 write!(f, "{} MiB", bytes / MIB)
78 } else if bytes >= KIB && bytes.is_multiple_of(KIB) {
79 write!(f, "{} KiB", bytes / KIB)
80 } else {
81 write!(f, "{} B", bytes)
82 }
83 }
84}
85
86impl From<ByteSize> for u64 {
87 fn from(size: ByteSize) -> Self {
88 size.0
89 }
90}
91
92impl From<u64> for ByteSize {
93 fn from(bytes: u64) -> Self {
94 Self(bytes)
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn test_from_bytes_round_trips() {
104 assert_eq!(ByteSize::from_bytes(4096).as_bytes(), 4096);
105 }
106
107 #[test]
108 fn test_unit_constructors_use_binary_base() {
109 assert_eq!(ByteSize::from_kib(1).as_bytes(), 1024);
110 assert_eq!(ByteSize::from_mib(64).as_bytes(), 64 * 1024 * 1024);
111 assert_eq!(ByteSize::from_gib(1).as_bytes(), 1024 * 1024 * 1024);
112 }
113
114 #[test]
115 fn test_as_kib_matches_sqlite_cache_semantics() {
116 assert_eq!(ByteSize::from_kib(2000).as_kib(), 2000);
118 }
119
120 #[test]
121 fn test_as_kib_truncates_sub_kib_remainder() {
122 assert_eq!(ByteSize::from_bytes(2047).as_kib(), 1);
123 assert_eq!(ByteSize::from_bytes(1023).as_kib(), 0);
124 }
125
126 #[test]
127 fn test_zero() {
128 assert_eq!(ByteSize::ZERO.as_bytes(), 0);
129 assert_eq!(ByteSize::ZERO, ByteSize::from_bytes(0));
130 }
131
132 #[test]
133 fn test_mul_scales_a_fixed_record_width_by_a_count() {
134 assert_eq!(ByteSize::from_bytes(33) * 4, ByteSize::from_bytes(132));
136 assert_eq!(ByteSize::from_kib(1) * 1024, ByteSize::from_mib(1));
137 assert_eq!(ByteSize::from_bytes(33) * 0, ByteSize::ZERO);
138 assert_eq!(ByteSize::ZERO * 99, ByteSize::ZERO);
139 }
140
141 #[test]
142 fn test_ordering_compares_by_byte_count() {
143 assert!(ByteSize::from_kib(1) < ByteSize::from_mib(1));
144 assert!(ByteSize::from_mib(256) > ByteSize::from_mib(64));
145 assert_eq!(ByteSize::from_kib(1024), ByteSize::from_mib(1));
146 }
147
148 #[test]
149 fn test_display_picks_largest_exact_unit() {
150 assert_eq!(ByteSize::from_gib(2).to_string(), "2 GiB");
151 assert_eq!(ByteSize::from_mib(64).to_string(), "64 MiB");
152 assert_eq!(ByteSize::from_kib(2000).to_string(), "2000 KiB");
153 assert_eq!(ByteSize::from_bytes(4096).to_string(), "4 KiB");
154 assert_eq!(ByteSize::from_bytes(1500).to_string(), "1500 B");
155 assert_eq!(ByteSize::ZERO.to_string(), "0 B");
156 }
157
158 #[test]
159 fn test_add_sums_bytes() {
160 assert_eq!(ByteSize::from_kib(1) + ByteSize::from_kib(3), ByteSize::from_kib(4));
161 assert_eq!(ByteSize::from_mib(1) + ByteSize::from_bytes(512), ByteSize::from_bytes(1024 * 1024 + 512));
162 assert_eq!(ByteSize::ZERO + ByteSize::from_bytes(7), ByteSize::from_bytes(7));
163 }
164
165 #[test]
166 fn test_saturating_add_clamps_at_max() {
167 assert_eq!(ByteSize::from_bytes(10).saturating_add(ByteSize::from_bytes(5)), ByteSize::from_bytes(15));
168 assert_eq!(
169 ByteSize::from_bytes(u64::MAX).saturating_add(ByteSize::from_bytes(1)),
170 ByteSize::from_bytes(u64::MAX)
171 );
172 }
173
174 #[test]
175 fn test_saturating_sub_clamps_at_zero() {
176 assert_eq!(ByteSize::from_bytes(10).saturating_sub(ByteSize::from_bytes(4)), ByteSize::from_bytes(6));
177 assert_eq!(ByteSize::from_bytes(3).saturating_sub(ByteSize::from_bytes(9)), ByteSize::ZERO);
178 }
179}