reifydb_value/
byte_size.rs1use std::{
5 fmt::{self, Display, Formatter},
6 ops::Add,
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 Display for ByteSize {
64 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
65 let bytes = self.0;
66 if bytes >= GIB && bytes.is_multiple_of(GIB) {
67 write!(f, "{} GiB", bytes / GIB)
68 } else if bytes >= MIB && bytes.is_multiple_of(MIB) {
69 write!(f, "{} MiB", bytes / MIB)
70 } else if bytes >= KIB && bytes.is_multiple_of(KIB) {
71 write!(f, "{} KiB", bytes / KIB)
72 } else {
73 write!(f, "{} B", bytes)
74 }
75 }
76}
77
78impl From<ByteSize> for u64 {
79 fn from(size: ByteSize) -> Self {
80 size.0
81 }
82}
83
84impl From<u64> for ByteSize {
85 fn from(bytes: u64) -> Self {
86 Self(bytes)
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn test_from_bytes_round_trips() {
96 assert_eq!(ByteSize::from_bytes(4096).as_bytes(), 4096);
97 }
98
99 #[test]
100 fn test_unit_constructors_use_binary_base() {
101 assert_eq!(ByteSize::from_kib(1).as_bytes(), 1024);
102 assert_eq!(ByteSize::from_mib(64).as_bytes(), 64 * 1024 * 1024);
103 assert_eq!(ByteSize::from_gib(1).as_bytes(), 1024 * 1024 * 1024);
104 }
105
106 #[test]
107 fn test_as_kib_matches_sqlite_cache_semantics() {
108 assert_eq!(ByteSize::from_kib(2000).as_kib(), 2000);
110 }
111
112 #[test]
113 fn test_as_kib_truncates_sub_kib_remainder() {
114 assert_eq!(ByteSize::from_bytes(2047).as_kib(), 1);
115 assert_eq!(ByteSize::from_bytes(1023).as_kib(), 0);
116 }
117
118 #[test]
119 fn test_zero() {
120 assert_eq!(ByteSize::ZERO.as_bytes(), 0);
121 assert_eq!(ByteSize::ZERO, ByteSize::from_bytes(0));
122 }
123
124 #[test]
125 fn test_ordering_compares_by_byte_count() {
126 assert!(ByteSize::from_kib(1) < ByteSize::from_mib(1));
127 assert!(ByteSize::from_mib(256) > ByteSize::from_mib(64));
128 assert_eq!(ByteSize::from_kib(1024), ByteSize::from_mib(1));
129 }
130
131 #[test]
132 fn test_display_picks_largest_exact_unit() {
133 assert_eq!(ByteSize::from_gib(2).to_string(), "2 GiB");
134 assert_eq!(ByteSize::from_mib(64).to_string(), "64 MiB");
135 assert_eq!(ByteSize::from_kib(2000).to_string(), "2000 KiB");
136 assert_eq!(ByteSize::from_bytes(4096).to_string(), "4 KiB");
137 assert_eq!(ByteSize::from_bytes(1500).to_string(), "1500 B");
138 assert_eq!(ByteSize::ZERO.to_string(), "0 B");
139 }
140
141 #[test]
142 fn test_add_sums_bytes() {
143 assert_eq!(ByteSize::from_kib(1) + ByteSize::from_kib(3), ByteSize::from_kib(4));
144 assert_eq!(ByteSize::from_mib(1) + ByteSize::from_bytes(512), ByteSize::from_bytes(1024 * 1024 + 512));
145 assert_eq!(ByteSize::ZERO + ByteSize::from_bytes(7), ByteSize::from_bytes(7));
146 }
147
148 #[test]
149 fn test_saturating_add_clamps_at_max() {
150 assert_eq!(ByteSize::from_bytes(10).saturating_add(ByteSize::from_bytes(5)), ByteSize::from_bytes(15));
151 assert_eq!(
152 ByteSize::from_bytes(u64::MAX).saturating_add(ByteSize::from_bytes(1)),
153 ByteSize::from_bytes(u64::MAX)
154 );
155 }
156
157 #[test]
158 fn test_saturating_sub_clamps_at_zero() {
159 assert_eq!(ByteSize::from_bytes(10).saturating_sub(ByteSize::from_bytes(4)), ByteSize::from_bytes(6));
160 assert_eq!(ByteSize::from_bytes(3).saturating_sub(ByteSize::from_bytes(9)), ByteSize::ZERO);
161 }
162}