1use crate::error::{BpError, BpResult};
26use serde::{Deserialize, Serialize};
27use std::fmt;
28
29pub const TIER_T1_BYTES: u64 = 10 * 1024 * 1024 * 1024;
33pub const TIER_T2_BYTES: u64 = 100 * 1024 * 1024 * 1024;
35pub const TIER_T3_BYTES: u64 = 500 * 1024 * 1024 * 1024;
37pub const TIER_T4_BYTES: u64 = 1024 * 1024 * 1024 * 1024;
39pub const TIER_T5_BYTES: u64 = 5 * 1024 * 1024 * 1024 * 1024;
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
50#[serde(rename_all = "UPPERCASE")]
51pub enum StorageTier {
52 T1,
54 T2,
56 T3,
58 T4,
60 T5,
62}
63
64impl StorageTier {
65 pub fn quota_bytes(self) -> u64 {
67 match self {
68 StorageTier::T1 => TIER_T1_BYTES,
69 StorageTier::T2 => TIER_T2_BYTES,
70 StorageTier::T3 => TIER_T3_BYTES,
71 StorageTier::T4 => TIER_T4_BYTES,
72 StorageTier::T5 => TIER_T5_BYTES,
73 }
74 }
75
76 pub fn name(self) -> &'static str {
78 match self {
79 StorageTier::T1 => "Pebble",
80 StorageTier::T2 => "Stone",
81 StorageTier::T3 => "Boulder",
82 StorageTier::T4 => "Rock",
83 StorageTier::T5 => "Monolith",
84 }
85 }
86
87 pub fn participating_tiers(self) -> &'static [StorageTier] {
91 match self {
92 StorageTier::T1 => &[StorageTier::T1],
93 StorageTier::T2 => &[StorageTier::T1, StorageTier::T2],
94 StorageTier::T3 => &[StorageTier::T1, StorageTier::T2, StorageTier::T3],
95 StorageTier::T4 => &[
96 StorageTier::T1,
97 StorageTier::T2,
98 StorageTier::T3,
99 StorageTier::T4,
100 ],
101 StorageTier::T5 => &[
102 StorageTier::T1,
103 StorageTier::T2,
104 StorageTier::T3,
105 StorageTier::T4,
106 StorageTier::T5,
107 ],
108 }
109 }
110
111 pub fn all() -> &'static [StorageTier] {
113 &[
114 StorageTier::T1,
115 StorageTier::T2,
116 StorageTier::T3,
117 StorageTier::T4,
118 StorageTier::T5,
119 ]
120 }
121
122 pub fn parse(s: &str) -> BpResult<Self> {
124 match s.to_uppercase().as_str() {
125 "T1" => Ok(StorageTier::T1),
126 "T2" => Ok(StorageTier::T2),
127 "T3" => Ok(StorageTier::T3),
128 "T4" => Ok(StorageTier::T4),
129 "T5" => Ok(StorageTier::T5),
130 other => Err(BpError::InvalidInput(format!(
131 "unknown storage tier '{}'; valid values are T1, T2, T3, T4, T5",
132 other
133 ))),
134 }
135 }
136
137 pub fn for_file_size(bytes: u64) -> Option<Self> {
141 if bytes <= TIER_T1_BYTES {
142 Some(StorageTier::T1)
143 } else if bytes <= TIER_T2_BYTES {
144 Some(StorageTier::T2)
145 } else if bytes <= TIER_T3_BYTES {
146 Some(StorageTier::T3)
147 } else if bytes <= TIER_T4_BYTES {
148 Some(StorageTier::T4)
149 } else if bytes <= TIER_T5_BYTES {
150 Some(StorageTier::T5)
151 } else {
152 None
153 }
154 }
155}
156
157impl fmt::Display for StorageTier {
158 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159 let (label, name) = match self {
160 StorageTier::T1 => ("T1", "Pebble — 10 GB"),
161 StorageTier::T2 => ("T2", "Stone — 100 GB"),
162 StorageTier::T3 => ("T3", "Boulder — 500 GB"),
163 StorageTier::T4 => ("T4", "Rock — 1 TB"),
164 StorageTier::T5 => ("T5", "Monolith — 5 TB"),
165 };
166 write!(f, "{} ({})", label, name)
167 }
168}
169
170impl std::str::FromStr for StorageTier {
171 type Err = BpError;
172
173 fn from_str(s: &str) -> Result<Self, Self::Err> {
174 StorageTier::parse(s)
175 }
176}
177
178#[cfg(test)]
181mod tests {
182 use super::*;
183
184 #[test]
185 fn tier_quota_bytes_correct() {
186 assert_eq!(StorageTier::T1.quota_bytes(), 10 * 1024 * 1024 * 1024);
187 assert_eq!(StorageTier::T2.quota_bytes(), 100 * 1024 * 1024 * 1024);
188 assert_eq!(StorageTier::T3.quota_bytes(), 500 * 1024 * 1024 * 1024);
189 assert_eq!(StorageTier::T4.quota_bytes(), 1024 * 1024 * 1024 * 1024);
190 assert_eq!(StorageTier::T5.quota_bytes(), 5 * 1024 * 1024 * 1024 * 1024);
191 }
192
193 #[test]
194 fn tier_ordering() {
195 assert!(StorageTier::T1 < StorageTier::T2);
196 assert!(StorageTier::T2 < StorageTier::T3);
197 assert!(StorageTier::T3 < StorageTier::T4);
198 assert!(StorageTier::T4 < StorageTier::T5);
199 }
200
201 #[test]
202 fn participating_tiers_correct() {
203 assert_eq!(StorageTier::T1.participating_tiers(), &[StorageTier::T1]);
204 assert_eq!(
205 StorageTier::T3.participating_tiers(),
206 &[StorageTier::T1, StorageTier::T2, StorageTier::T3]
207 );
208 assert_eq!(StorageTier::T5.participating_tiers().len(), 5);
209 }
210
211 #[test]
212 fn parse_case_insensitive() {
213 assert_eq!(StorageTier::parse("t1").unwrap(), StorageTier::T1);
214 assert_eq!(StorageTier::parse("T3").unwrap(), StorageTier::T3);
215 assert_eq!(StorageTier::parse("T5").unwrap(), StorageTier::T5);
216 assert!(StorageTier::parse("T6").is_err());
217 assert!(StorageTier::parse("pebble").is_err());
218 }
219
220 #[test]
221 fn for_file_size() {
222 assert_eq!(StorageTier::for_file_size(1024), Some(StorageTier::T1));
223 assert_eq!(
224 StorageTier::for_file_size(TIER_T1_BYTES),
225 Some(StorageTier::T1)
226 );
227 assert_eq!(
228 StorageTier::for_file_size(TIER_T1_BYTES + 1),
229 Some(StorageTier::T2)
230 );
231 assert_eq!(
232 StorageTier::for_file_size(TIER_T5_BYTES),
233 Some(StorageTier::T5)
234 );
235 assert_eq!(StorageTier::for_file_size(TIER_T5_BYTES + 1), None);
236 }
237
238 #[test]
239 fn serde_roundtrip() {
240 let tier = StorageTier::T3;
241 let json = serde_json::to_string(&tier).unwrap();
242 assert_eq!(json, "\"T3\"");
243 let back: StorageTier = serde_json::from_str(&json).unwrap();
244 assert_eq!(back, StorageTier::T3);
245 }
246
247 #[test]
248 fn from_str_trait() {
249 use std::str::FromStr;
250 assert_eq!(StorageTier::from_str("T2").unwrap(), StorageTier::T2);
251 }
252}