Skip to main content

rebecca_core/
execution.rs

1use std::path::PathBuf;
2
3use serde::{Deserialize, Serialize};
4
5use crate::TargetStatus;
6use crate::plan::{CleanupTarget, CleanupTargetDeletionStyle};
7
8const EXECUTION_TARGET_SHADOWED_REASON: &str = "execution-target-shadowed";
9
10#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
11pub struct ExecutionReport {
12    #[serde(default)]
13    pub dry_run: bool,
14    pub summary: ExecutionSummary,
15    #[serde(default, skip_serializing_if = "Vec::is_empty")]
16    pub actions: Vec<ExecutionActionReport>,
17    #[serde(default, skip_serializing_if = "Vec::is_empty")]
18    pub warnings: Vec<ExecutionWarning>,
19}
20
21impl ExecutionReport {
22    pub fn dry_run() -> Self {
23        Self {
24            dry_run: true,
25            ..Self::default()
26        }
27    }
28
29    pub fn from_targets(targets: &[CleanupTarget]) -> Self {
30        let actions = targets
31            .iter()
32            .enumerate()
33            .filter(|(_, target)| target.status != TargetStatus::Allowed)
34            .map(|(index, target)| ExecutionActionReport::from_target(index, target))
35            .collect::<Vec<_>>();
36
37        Self::from_actions(actions)
38    }
39
40    pub fn from_actions(actions: Vec<ExecutionActionReport>) -> Self {
41        Self::from_actions_with_dry_run(actions, false)
42    }
43
44    pub fn from_actions_with_dry_run(actions: Vec<ExecutionActionReport>, dry_run: bool) -> Self {
45        let summary = ExecutionSummary::from_actions(&actions);
46        Self {
47            dry_run,
48            summary,
49            actions,
50            warnings: Vec::new(),
51        }
52    }
53
54    pub fn push_warning(&mut self, warning: ExecutionWarning) {
55        self.warnings.push(warning);
56    }
57}
58
59#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
60pub struct ExecutionSummary {
61    pub total_actions: usize,
62    pub completed_actions: usize,
63    pub skipped_actions: usize,
64    pub blocked_actions: usize,
65    pub failed_actions: usize,
66    pub estimated_bytes: u64,
67    pub confirmed_reclaimed_bytes: u64,
68    pub pending_reclaim_bytes: u64,
69    pub skipped_bytes: u64,
70    pub failed_bytes: u64,
71    pub shadowed_bytes: u64,
72}
73
74impl ExecutionSummary {
75    pub fn from_actions(actions: &[ExecutionActionReport]) -> Self {
76        let mut summary = Self {
77            total_actions: actions.len(),
78            ..Self::default()
79        };
80
81        for action in actions {
82            summary.estimated_bytes = summary
83                .estimated_bytes
84                .saturating_add(action.estimated_bytes);
85            summary.confirmed_reclaimed_bytes = summary
86                .confirmed_reclaimed_bytes
87                .saturating_add(action.confirmed_reclaimed_bytes);
88            summary.pending_reclaim_bytes = summary
89                .pending_reclaim_bytes
90                .saturating_add(action.pending_reclaim_bytes);
91
92            match action.status {
93                TargetStatus::Completed => summary.completed_actions += 1,
94                TargetStatus::Skipped => {
95                    summary.skipped_actions += 1;
96                    summary.skipped_bytes =
97                        summary.skipped_bytes.saturating_add(action.estimated_bytes);
98                    if action.reason_code.as_deref() == Some(EXECUTION_TARGET_SHADOWED_REASON) {
99                        summary.shadowed_bytes = summary
100                            .shadowed_bytes
101                            .saturating_add(action.estimated_bytes);
102                    }
103                }
104                TargetStatus::Blocked => {
105                    summary.blocked_actions += 1;
106                    summary.skipped_bytes =
107                        summary.skipped_bytes.saturating_add(action.estimated_bytes);
108                }
109                TargetStatus::Failed => {
110                    summary.failed_actions += 1;
111                    summary.failed_bytes =
112                        summary.failed_bytes.saturating_add(action.estimated_bytes);
113                }
114                TargetStatus::Allowed => {}
115            }
116        }
117
118        summary
119    }
120}
121
122#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
123pub struct ExecutionActionReport {
124    pub target_index: usize,
125    pub rule_id: String,
126    pub path: PathBuf,
127    pub deletion_style: CleanupTargetDeletionStyle,
128    pub estimated_bytes: u64,
129    pub status: TargetStatus,
130    #[serde(default, skip_serializing_if = "Option::is_none")]
131    pub reason: Option<String>,
132    #[serde(default, skip_serializing_if = "Option::is_none")]
133    pub reason_code: Option<String>,
134    #[serde(default, skip_serializing_if = "Vec::is_empty")]
135    pub attempted_paths: Vec<PathBuf>,
136    pub confirmed_reclaimed_bytes: u64,
137    pub pending_reclaim_bytes: u64,
138}
139
140impl ExecutionActionReport {
141    pub fn from_target(target_index: usize, target: &CleanupTarget) -> Self {
142        let attempted_paths = match target.status {
143            TargetStatus::Completed | TargetStatus::Failed => vec![target.path.clone()],
144            TargetStatus::Allowed | TargetStatus::Skipped | TargetStatus::Blocked => Vec::new(),
145        };
146
147        Self {
148            target_index,
149            rule_id: target.rule_id.clone(),
150            path: target.path.clone(),
151            deletion_style: target.deletion_style,
152            estimated_bytes: target.estimated_bytes,
153            status: target.status,
154            reason: target.reason.clone(),
155            reason_code: target.reason_code.map(|reason| reason.label().to_string()),
156            attempted_paths,
157            confirmed_reclaimed_bytes: target.freed_bytes,
158            pending_reclaim_bytes: target.pending_reclaim_bytes,
159        }
160    }
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
164pub struct ExecutionWarning {
165    pub kind: ExecutionWarningKind,
166    pub message: String,
167}
168
169impl ExecutionWarning {
170    pub fn history_write_failed(message: impl Into<String>) -> Self {
171        Self {
172            kind: ExecutionWarningKind::HistoryWriteFailed,
173            message: message.into(),
174        }
175    }
176}
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
179#[serde(rename_all = "kebab-case")]
180pub enum ExecutionWarningKind {
181    HistoryWriteFailed,
182}