1use serde::{Deserialize as SerdeDeserialize, Serialize};
9use std::{fmt, path::PathBuf};
10
11pub const CACHE_STATUS_REPORT_SCHEMA_VERSION: u32 = 1;
13
14#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
21#[serde(rename_all = "snake_case")]
22pub enum CacheValidationStatus {
23 #[serde(rename = "ok")]
25 Valid,
26 Invalid,
28}
29
30impl CacheValidationStatus {
31 #[must_use]
33 pub const fn as_str(self) -> &'static str {
34 match self {
35 Self::Valid => "ok",
36 Self::Invalid => "invalid",
37 }
38 }
39}
40
41impl fmt::Display for CacheValidationStatus {
42 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
43 formatter.write_str(self.as_str())
44 }
45}
46
47#[derive(Clone, Copy, Debug, Eq, PartialEq, SerdeDeserialize, Serialize)]
54#[serde(rename_all = "snake_case")]
55pub enum CacheRefreshAttemptStatus {
56 Running,
58 Complete,
60 Failed,
62}
63
64impl CacheRefreshAttemptStatus {
65 #[must_use]
67 pub const fn as_str(self) -> &'static str {
68 match self {
69 Self::Running => "running",
70 Self::Complete => "complete",
71 Self::Failed => "failed",
72 }
73 }
74}
75
76impl fmt::Display for CacheRefreshAttemptStatus {
77 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
78 formatter.write_str(self.as_str())
79 }
80}
81
82#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
89#[serde(rename_all = "snake_case")]
90pub enum CacheHeaderStatus {
91 Readable,
93 Invalid,
95}
96
97impl CacheHeaderStatus {
98 #[must_use]
100 pub const fn as_str(self) -> &'static str {
101 match self {
102 Self::Readable => "readable",
103 Self::Invalid => "invalid",
104 }
105 }
106}
107
108#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
115#[serde(rename_all = "snake_case")]
116pub enum CacheAgeStatus {
117 Fresh,
119 Stale,
121 Unmanaged,
123 Unknown,
125}
126
127impl CacheAgeStatus {
128 #[must_use]
130 pub const fn as_str(self) -> &'static str {
131 match self {
132 Self::Fresh => "fresh",
133 Self::Stale => "stale",
134 Self::Unmanaged => "unmanaged",
135 Self::Unknown => "unknown",
136 }
137 }
138}
139
140#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
147#[serde(rename_all = "snake_case")]
148pub enum CacheRecoveryPolicy {
149 Automatic,
151 Explicit,
153 MissingOnly,
155 Unknown,
157}
158
159impl CacheRecoveryPolicy {
160 #[must_use]
162 pub const fn as_str(self) -> &'static str {
163 match self {
164 Self::Automatic => "automatic",
165 Self::Explicit => "explicit",
166 Self::MissingOnly => "missing_only",
167 Self::Unknown => "unknown",
168 }
169 }
170}
171
172#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
179#[serde(rename_all = "snake_case")]
180pub enum CacheRefreshLockStatus {
181 Active,
183 Available,
185 Invalid,
187}
188
189impl CacheRefreshLockStatus {
190 #[must_use]
192 pub const fn as_str(self) -> &'static str {
193 match self {
194 Self::Active => "active",
195 Self::Available => "available",
196 Self::Invalid => "invalid",
197 }
198 }
199}
200
201#[derive(Clone, Debug, Eq, PartialEq)]
208pub struct CacheStatusRequest {
209 pub cache_root: PathBuf,
211 pub now_unix_secs: u64,
213}
214
215impl CacheStatusRequest {
216 #[must_use]
218 pub fn new(cache_root: impl Into<PathBuf>, now_unix_secs: u64) -> Self {
219 Self {
220 cache_root: cache_root.into(),
221 now_unix_secs,
222 }
223 }
224}
225
226#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
233pub struct CacheStatusReport {
234 pub schema_version: u32,
236 pub cache_root: String,
238 pub inspected_at: String,
240 pub cache_root_found: bool,
242 pub scan_limit: usize,
244 pub truncated: bool,
246 pub family_validation_performed: bool,
248 pub readable_header_count: usize,
250 pub invalid_header_count: usize,
252 pub fresh_count: usize,
254 pub stale_count: usize,
256 pub unmanaged_age_count: usize,
258 pub unknown_age_count: usize,
260 pub total_size_bytes: u64,
262 pub caches: Vec<CacheStatusRow>,
264 pub active_refresh_lock_count: usize,
266 pub available_refresh_lock_count: usize,
268 pub invalid_refresh_lock_count: usize,
270 pub refresh_lock_size_bytes: u64,
272 pub refresh_locks: Vec<CacheRefreshLockStatusRow>,
274}
275
276#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
283pub struct CacheStatusRow {
284 pub component: String,
286 pub cache_path: String,
288 pub relative_path: String,
290 pub header_status: CacheHeaderStatus,
292 pub age_status: CacheAgeStatus,
294 pub recovery_policy: CacheRecoveryPolicy,
296 pub schema_version: Option<u32>,
298 pub network: Option<String>,
300 pub fetched_at: Option<String>,
302 pub age_seconds: Option<u64>,
304 pub stale_after_seconds: Option<u64>,
306 pub size_bytes: u64,
308 pub inspection_error: Option<String>,
310}
311
312#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
319pub struct CacheRefreshLockStatusRow {
320 pub component: String,
322 pub refresh_lock_path: String,
324 pub relative_path: String,
326 pub status: CacheRefreshLockStatus,
328 pub size_bytes: u64,
330 pub error: Option<String>,
332}
333
334#[cfg(test)]
335mod tests {
336 use super::*;
337
338 #[test]
339 fn typed_statuses_keep_stable_json_labels() {
340 for (status, expected) in [
341 (CacheValidationStatus::Valid, "ok"),
342 (CacheValidationStatus::Invalid, "invalid"),
343 ] {
344 assert_eq!(status.as_str(), expected);
345 assert_eq!(status.to_string(), expected);
346 assert_eq!(
347 serde_json::to_value(status).expect("serialize cache validation status"),
348 serde_json::json!(expected)
349 );
350 }
351 for (status, expected) in [
352 (CacheRefreshAttemptStatus::Running, "running"),
353 (CacheRefreshAttemptStatus::Complete, "complete"),
354 (CacheRefreshAttemptStatus::Failed, "failed"),
355 ] {
356 assert_eq!(status.as_str(), expected);
357 assert_eq!(status.to_string(), expected);
358 assert_eq!(
359 serde_json::from_value::<CacheRefreshAttemptStatus>(serde_json::json!(expected))
360 .expect("deserialize refresh-attempt status"),
361 status
362 );
363 assert_eq!(
364 serde_json::to_value(status).expect("serialize refresh-attempt status"),
365 serde_json::json!(expected)
366 );
367 }
368 assert!(
369 serde_json::from_value::<CacheRefreshAttemptStatus>(serde_json::json!("unknown"))
370 .is_err()
371 );
372 for (status, expected) in [
373 (CacheHeaderStatus::Readable, "readable"),
374 (CacheHeaderStatus::Invalid, "invalid"),
375 ] {
376 assert_eq!(status.as_str(), expected);
377 assert_eq!(
378 serde_json::to_value(status).expect("serialize cache header status"),
379 serde_json::json!(expected)
380 );
381 }
382 for (status, expected) in [
383 (CacheAgeStatus::Fresh, "fresh"),
384 (CacheAgeStatus::Stale, "stale"),
385 (CacheAgeStatus::Unmanaged, "unmanaged"),
386 (CacheAgeStatus::Unknown, "unknown"),
387 ] {
388 assert_eq!(status.as_str(), expected);
389 assert_eq!(
390 serde_json::to_value(status).expect("serialize cache age status"),
391 serde_json::json!(expected)
392 );
393 }
394 for (policy, expected) in [
395 (CacheRecoveryPolicy::Automatic, "automatic"),
396 (CacheRecoveryPolicy::Explicit, "explicit"),
397 (CacheRecoveryPolicy::MissingOnly, "missing_only"),
398 (CacheRecoveryPolicy::Unknown, "unknown"),
399 ] {
400 assert_eq!(policy.as_str(), expected);
401 assert_eq!(
402 serde_json::to_value(policy).expect("serialize cache recovery policy"),
403 serde_json::json!(expected)
404 );
405 }
406 for (status, expected) in [
407 (CacheRefreshLockStatus::Active, "active"),
408 (CacheRefreshLockStatus::Available, "available"),
409 (CacheRefreshLockStatus::Invalid, "invalid"),
410 ] {
411 assert_eq!(status.as_str(), expected);
412 assert_eq!(
413 serde_json::to_value(status).expect("serialize refresh-lock status"),
414 serde_json::json!(expected)
415 );
416 }
417 }
418}
419
420#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
427pub struct SnapshotRefreshAttemptStatus {
428 pub status: CacheRefreshAttemptStatus,
430 pub started_at: String,
432 pub updated_at: String,
434 pub page_size: u32,
436 pub pages_fetched: u32,
438 pub rows_fetched: usize,
440 pub last_cursor: Option<String>,
442 pub last_error: Option<String>,
444}