Skip to main content

bijux_cli/features/diagnostics/
state_diagnostics.rs

1#![forbid(unsafe_code)]
2//! State diagnostics query model and resolver.
3
4use std::fs;
5use std::path::{Path, PathBuf};
6
7/// File-system status for a single state path.
8#[derive(Debug, Clone, PartialEq, Eq)]
9#[allow(clippy::struct_excessive_bools)]
10pub struct StatePathStatus {
11    /// Absolute or resolved path.
12    pub path: PathBuf,
13    /// Whether the path exists.
14    pub exists: bool,
15    /// Whether the path is a regular file.
16    pub is_file: bool,
17    /// Whether the path is a directory.
18    pub is_dir: bool,
19    /// File size in bytes.
20    pub size_bytes: u64,
21    /// Whether the path can be read by the current process.
22    pub readable: bool,
23    /// Whether the path can be written by the current process.
24    pub writable: bool,
25}
26
27/// Structured state diagnostics queried from runtime state paths.
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct StateDiagnosticsQuery {
30    /// Config file status.
31    pub config: StatePathStatus,
32    /// History file status.
33    pub history: StatePathStatus,
34    /// Plugin registry status.
35    pub plugins_registry: StatePathStatus,
36    /// Memory file status.
37    pub memory: StatePathStatus,
38}
39
40fn parent_dir_is_writable(path: &Path) -> bool {
41    let mut cursor = path.parent();
42    while let Some(parent) = cursor {
43        if let Ok(metadata) = fs::metadata(parent) {
44            return metadata.is_dir() && !metadata.permissions().readonly();
45        }
46        cursor = parent.parent();
47    }
48    false
49}
50
51fn state_path_status(path: &Path) -> StatePathStatus {
52    let metadata = fs::metadata(path);
53    StatePathStatus {
54        path: path.to_path_buf(),
55        exists: metadata.is_ok(),
56        is_file: metadata.as_ref().is_ok_and(std::fs::Metadata::is_file),
57        is_dir: metadata.as_ref().is_ok_and(std::fs::Metadata::is_dir),
58        size_bytes: metadata.as_ref().map_or(0_u64, |entry| entry.len()),
59        readable: fs::read(path).is_ok(),
60        writable: if path.exists() {
61            fs::OpenOptions::new().append(true).open(path).is_ok()
62        } else {
63            parent_dir_is_writable(path)
64        },
65    }
66}
67
68/// Query state diagnostics from resolved runtime paths.
69#[must_use]
70pub fn state_diagnostics_query(
71    config: &Path,
72    history: &Path,
73    plugins_registry: &Path,
74    memory: &Path,
75) -> StateDiagnosticsQuery {
76    StateDiagnosticsQuery {
77        config: state_path_status(config),
78        history: state_path_status(history),
79        plugins_registry: state_path_status(plugins_registry),
80        memory: state_path_status(memory),
81    }
82}