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 cache_count: usize,
250 pub readable_header_count: usize,
252 pub invalid_header_count: usize,
254 pub fresh_count: usize,
256 pub stale_count: usize,
258 pub unmanaged_age_count: usize,
260 pub unknown_age_count: usize,
262 pub total_size_bytes: u64,
264 pub caches: Vec<CacheStatusRow>,
266 pub refresh_lock_count: usize,
268 pub active_refresh_lock_count: usize,
270 pub available_refresh_lock_count: usize,
272 pub invalid_refresh_lock_count: usize,
274 pub refresh_lock_size_bytes: u64,
276 pub refresh_locks: Vec<CacheRefreshLockStatusRow>,
278}
279
280#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
287pub struct CacheStatusRow {
288 pub component: String,
290 pub cache_path: String,
292 pub relative_path: String,
294 pub header_status: CacheHeaderStatus,
296 pub age_status: CacheAgeStatus,
298 pub recovery_policy: CacheRecoveryPolicy,
300 pub schema_version: Option<u32>,
302 pub network: Option<String>,
304 pub fetched_at: Option<String>,
306 pub age_seconds: Option<u64>,
308 pub stale_after_seconds: Option<u64>,
310 pub size_bytes: u64,
312 pub inspection_error: Option<String>,
314}
315
316#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
323pub struct CacheRefreshLockStatusRow {
324 pub component: String,
326 pub refresh_lock_path: String,
328 pub relative_path: String,
330 pub status: CacheRefreshLockStatus,
332 pub size_bytes: u64,
334 pub error: Option<String>,
336}
337
338#[cfg(test)]
339mod tests {
340 use super::*;
341
342 #[test]
343 fn typed_statuses_keep_stable_json_labels() {
344 for (status, expected) in [
345 (CacheValidationStatus::Valid, "ok"),
346 (CacheValidationStatus::Invalid, "invalid"),
347 ] {
348 assert_eq!(status.as_str(), expected);
349 assert_eq!(status.to_string(), expected);
350 assert_eq!(
351 serde_json::to_value(status).expect("serialize cache validation status"),
352 serde_json::json!(expected)
353 );
354 }
355 for (status, expected) in [
356 (CacheRefreshAttemptStatus::Running, "running"),
357 (CacheRefreshAttemptStatus::Complete, "complete"),
358 (CacheRefreshAttemptStatus::Failed, "failed"),
359 ] {
360 assert_eq!(status.as_str(), expected);
361 assert_eq!(status.to_string(), expected);
362 assert_eq!(
363 serde_json::from_value::<CacheRefreshAttemptStatus>(serde_json::json!(expected))
364 .expect("deserialize refresh-attempt status"),
365 status
366 );
367 assert_eq!(
368 serde_json::to_value(status).expect("serialize refresh-attempt status"),
369 serde_json::json!(expected)
370 );
371 }
372 assert!(
373 serde_json::from_value::<CacheRefreshAttemptStatus>(serde_json::json!("unknown"))
374 .is_err()
375 );
376 for (status, expected) in [
377 (CacheHeaderStatus::Readable, "readable"),
378 (CacheHeaderStatus::Invalid, "invalid"),
379 ] {
380 assert_eq!(status.as_str(), expected);
381 assert_eq!(
382 serde_json::to_value(status).expect("serialize cache header status"),
383 serde_json::json!(expected)
384 );
385 }
386 for (status, expected) in [
387 (CacheAgeStatus::Fresh, "fresh"),
388 (CacheAgeStatus::Stale, "stale"),
389 (CacheAgeStatus::Unmanaged, "unmanaged"),
390 (CacheAgeStatus::Unknown, "unknown"),
391 ] {
392 assert_eq!(status.as_str(), expected);
393 assert_eq!(
394 serde_json::to_value(status).expect("serialize cache age status"),
395 serde_json::json!(expected)
396 );
397 }
398 for (policy, expected) in [
399 (CacheRecoveryPolicy::Automatic, "automatic"),
400 (CacheRecoveryPolicy::Explicit, "explicit"),
401 (CacheRecoveryPolicy::MissingOnly, "missing_only"),
402 (CacheRecoveryPolicy::Unknown, "unknown"),
403 ] {
404 assert_eq!(policy.as_str(), expected);
405 assert_eq!(
406 serde_json::to_value(policy).expect("serialize cache recovery policy"),
407 serde_json::json!(expected)
408 );
409 }
410 for (status, expected) in [
411 (CacheRefreshLockStatus::Active, "active"),
412 (CacheRefreshLockStatus::Available, "available"),
413 (CacheRefreshLockStatus::Invalid, "invalid"),
414 ] {
415 assert_eq!(status.as_str(), expected);
416 assert_eq!(
417 serde_json::to_value(status).expect("serialize refresh-lock status"),
418 serde_json::json!(expected)
419 );
420 }
421 }
422}
423
424#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
431pub struct SnapshotRefreshAttemptStatus {
432 pub status: CacheRefreshAttemptStatus,
434 pub started_at: String,
436 pub updated_at: String,
438 pub page_size: u32,
440 pub pages_fetched: u32,
442 pub rows_fetched: usize,
444 pub last_cursor: Option<String>,
446 pub last_error: Option<String>,
448}