Skip to main content

weavatrix_worktree/report/
operation.rs

1use core::fmt;
2
3use crate::hash::Sha256Hash;
4
5/// Logical filesystem operation represented by a worktree plan.
6#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
7pub enum OperationKind {
8    Modify,
9    Create,
10    Delete,
11    Rename,
12}
13
14impl OperationKind {
15    #[must_use]
16    pub const fn as_str(self) -> &'static str {
17        match self {
18            Self::Modify => "MODIFY",
19            Self::Create => "CREATE",
20            Self::Delete => "DELETE",
21            Self::Rename => "RENAME",
22        }
23    }
24}
25
26impl fmt::Display for OperationKind {
27    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
28        formatter.write_str(self.as_str())
29    }
30}
31
32/// Deterministic before/after evidence for one logical filesystem operation.
33#[derive(Clone, Debug, Eq, PartialEq)]
34pub struct OperationChange {
35    kind: OperationKind,
36    source_path: Option<String>,
37    destination_path: Option<String>,
38    old_sha256: Option<Sha256Hash>,
39    new_sha256: Option<Sha256Hash>,
40    bytes_before: u64,
41    bytes_after: u64,
42    edits_applied: usize,
43}
44
45impl OperationChange {
46    #[allow(clippy::too_many_arguments)]
47    pub(crate) fn new(
48        kind: OperationKind,
49        source_path: Option<String>,
50        destination_path: Option<String>,
51        old_sha256: Option<Sha256Hash>,
52        new_sha256: Option<Sha256Hash>,
53        bytes_before: u64,
54        bytes_after: u64,
55        edits_applied: usize,
56    ) -> Self {
57        Self {
58            kind,
59            source_path,
60            destination_path,
61            old_sha256,
62            new_sha256,
63            bytes_before,
64            bytes_after,
65            edits_applied,
66        }
67    }
68
69    #[must_use]
70    pub const fn kind(&self) -> OperationKind {
71        self.kind
72    }
73
74    #[must_use]
75    pub fn source_path(&self) -> Option<&str> {
76        self.source_path.as_deref()
77    }
78
79    #[must_use]
80    pub fn destination_path(&self) -> Option<&str> {
81        self.destination_path.as_deref()
82    }
83
84    #[must_use]
85    pub const fn old_sha256(&self) -> Option<Sha256Hash> {
86        self.old_sha256
87    }
88
89    #[must_use]
90    pub const fn new_sha256(&self) -> Option<Sha256Hash> {
91        self.new_sha256
92    }
93
94    #[must_use]
95    pub const fn bytes_before(&self) -> u64 {
96        self.bytes_before
97    }
98
99    #[must_use]
100    pub const fn bytes_after(&self) -> u64 {
101        self.bytes_after
102    }
103
104    #[must_use]
105    pub const fn edits_applied(&self) -> usize {
106        self.edits_applied
107    }
108}
109
110/// Side-effect-free projection of a resource-operation plan.
111#[derive(Clone, Debug, Eq, PartialEq)]
112pub struct WorktreeDryRunReport {
113    operation: String,
114    files: Vec<OperationChange>,
115    touched_paths: usize,
116}
117
118impl WorktreeDryRunReport {
119    pub(crate) fn new(
120        operation: impl Into<String>,
121        files: Vec<OperationChange>,
122        touched_paths: usize,
123    ) -> Self {
124        Self {
125            operation: operation.into(),
126            files,
127            touched_paths,
128        }
129    }
130
131    #[must_use]
132    pub fn operation(&self) -> &str {
133        &self.operation
134    }
135
136    #[must_use]
137    pub fn files(&self) -> &[OperationChange] {
138        &self.files
139    }
140
141    #[must_use]
142    pub const fn touched_paths(&self) -> usize {
143        self.touched_paths
144    }
145
146    #[must_use]
147    pub fn total_edits(&self) -> usize {
148        self.files
149            .iter()
150            .fold(0, |total, file| total.saturating_add(file.edits_applied()))
151    }
152}
153
154/// Successful durable commit of a resource-operation plan.
155#[derive(Clone, Debug, Eq, PartialEq)]
156pub struct WorktreeApplyReport {
157    transaction_id: String,
158    operation: String,
159    files: Vec<OperationChange>,
160    touched_paths: usize,
161}
162
163impl WorktreeApplyReport {
164    pub(crate) fn new(
165        transaction_id: impl Into<String>,
166        operation: impl Into<String>,
167        files: Vec<OperationChange>,
168        touched_paths: usize,
169    ) -> Self {
170        Self {
171            transaction_id: transaction_id.into(),
172            operation: operation.into(),
173            files,
174            touched_paths,
175        }
176    }
177
178    #[must_use]
179    pub fn transaction_id(&self) -> &str {
180        &self.transaction_id
181    }
182
183    #[must_use]
184    pub fn operation(&self) -> &str {
185        &self.operation
186    }
187
188    #[must_use]
189    pub fn files(&self) -> &[OperationChange] {
190        &self.files
191    }
192
193    #[must_use]
194    pub const fn touched_paths(&self) -> usize {
195        self.touched_paths
196    }
197}