prikk_object/payload/patch/
operations.rs1use prikk_error::{PrikkError, Result};
7
8use crate::path::validate_repo_path;
9use crate::payload::node::{NodeId, NodeKind};
10use crate::{CanonicalEncode, CanonicalWriter, ObjectId};
11
12use super::{TEXT_SPAN_HASH_BYTES, text_span_hash};
13
14#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct CreateFile {
17 pub path: String,
19 pub node_id: NodeId,
21 pub blob_id: ObjectId,
23 pub mode: u32,
25}
26
27impl CreateFile {
28 pub fn validate(&self) -> Result<()> {
33 if self.node_id.is_zero() {
34 return Err(PrikkError::CanonicalEncoding(
35 "CreateFile node_id must be nonzero".to_string(),
36 ));
37 }
38 validate_repo_path(&self.path)?;
39 Ok(())
40 }
41}
42
43impl CanonicalEncode for CreateFile {
44 fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
45 self.validate()?;
46 writer.field_repo_path(1, &self.path)?;
47 writer.field_bytes(2, self.node_id.as_bytes())?;
48 writer.field_object_id(3, &self.blob_id)?;
49 writer.field_u32(4, self.mode)?;
50 Ok(())
51 }
52}
53
54#[derive(Debug, Clone, PartialEq, Eq)]
56pub enum DeleteNodePreimage {
57 File {
59 old_blob_id: ObjectId,
61 old_mode: u32,
63 },
64 Symlink {
66 old_target: String,
68 },
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct DeleteNode {
76 pub path: String,
78 pub node_id: NodeId,
80 pub old_node_kind: NodeKind,
82 pub preimage: DeleteNodePreimage,
84}
85
86impl DeleteNode {
87 pub fn validate(&self) -> Result<()> {
91 if self.node_id.is_zero() {
92 return Err(PrikkError::CanonicalEncoding(
93 "DeleteNode node_id must be nonzero".to_string(),
94 ));
95 }
96 validate_repo_path(&self.path)?;
97 let consistent = matches!(
98 (self.old_node_kind, &self.preimage),
99 (
100 NodeKind::TextFile | NodeKind::BinaryFile,
101 DeleteNodePreimage::File { .. }
102 ) | (NodeKind::Symlink, DeleteNodePreimage::Symlink { .. })
103 );
104 if !consistent {
105 return Err(PrikkError::CanonicalEncoding(
106 "DeleteNode old_node_kind does not match preimage discriminator".to_string(),
107 ));
108 }
109 Ok(())
110 }
111}
112
113impl CanonicalEncode for DeleteNode {
114 fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
115 self.validate()?;
116 writer.field_repo_path(1, &self.path)?;
117 writer.field_bytes(2, self.node_id.as_bytes())?;
118 writer.field_enum_u16(3, self.old_node_kind.code())?;
119 match &self.preimage {
120 DeleteNodePreimage::File {
121 old_blob_id,
122 old_mode,
123 } => {
124 writer.field_object_id(4, old_blob_id)?;
125 writer.field_u32(6, *old_mode)?;
126 }
127 DeleteNodePreimage::Symlink { old_target } => {
128 writer.field_string(5, old_target)?;
129 }
130 }
131 Ok(())
132 }
133}
134
135#[derive(Debug, Clone, PartialEq, Eq)]
137pub struct EditText {
138 pub node_id: NodeId,
140 pub span_id: [u8; TEXT_SPAN_HASH_BYTES],
142 pub old_span_hash: [u8; TEXT_SPAN_HASH_BYTES],
144 pub left_anchor_hash: [u8; TEXT_SPAN_HASH_BYTES],
146 pub right_anchor_hash: [u8; TEXT_SPAN_HASH_BYTES],
148 pub replacement_text: Vec<u8>,
150 pub presentation_hint_line: Option<u32>,
152 pub presentation_hint_column: Option<u32>,
154 pub old_span_text: Vec<u8>,
156}
157
158impl EditText {
159 pub fn validate(&self) -> Result<()> {
163 if self.node_id.is_zero() {
164 return Err(PrikkError::CanonicalEncoding(
165 "EditText node_id must be nonzero".to_string(),
166 ));
167 }
168 if self.old_span_hash != text_span_hash(&self.old_span_text) {
169 return Err(PrikkError::CanonicalEncoding(
170 "EditText old_span_hash must equal SHA-256(old_span_text)".to_string(),
171 ));
172 }
173 if core::str::from_utf8(&self.old_span_text).is_err() {
174 return Err(PrikkError::CanonicalEncoding(
175 "EditText old_span_text must be well-formed UTF-8".to_string(),
176 ));
177 }
178 if core::str::from_utf8(&self.replacement_text).is_err() {
179 return Err(PrikkError::CanonicalEncoding(
180 "EditText replacement_text must be well-formed UTF-8".to_string(),
181 ));
182 }
183 Ok(())
184 }
185}
186
187impl CanonicalEncode for EditText {
188 fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
189 self.validate()?;
190 writer.field_bytes(1, self.node_id.as_bytes())?;
191 writer.field_bytes(2, &self.span_id)?;
192 writer.field_bytes(3, &self.old_span_hash)?;
193 writer.field_bytes(4, &self.left_anchor_hash)?;
194 writer.field_bytes(5, &self.right_anchor_hash)?;
195 writer.field_bytes(6, &self.replacement_text)?;
196 if let Some(line) = self.presentation_hint_line {
197 writer.field_u32(7, line)?;
198 }
199 if let Some(column) = self.presentation_hint_column {
200 writer.field_u32(8, column)?;
201 }
202 writer.field_bytes(9, &self.old_span_text)?;
203 Ok(())
204 }
205}
206
207#[derive(Debug, Clone, PartialEq, Eq)]
209pub struct RenamePath {
210 pub node_id: NodeId,
212 pub old_path: String,
214 pub new_path: String,
216}
217
218impl RenamePath {
219 pub fn validate(&self) -> Result<()> {
225 if self.node_id.is_zero() {
226 return Err(PrikkError::CanonicalEncoding(
227 "RenamePath node_id must be nonzero".to_string(),
228 ));
229 }
230 validate_repo_path(&self.old_path)?;
231 validate_repo_path(&self.new_path)?;
232 Ok(())
233 }
234}
235
236impl CanonicalEncode for RenamePath {
237 fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
238 self.validate()?;
239 writer.field_bytes(1, self.node_id.as_bytes())?;
240 writer.field_repo_path(2, &self.old_path)?;
241 writer.field_repo_path(3, &self.new_path)?;
242 Ok(())
243 }
244}
245
246#[derive(Debug, Clone, PartialEq, Eq)]
248pub struct ChangePerm {
249 pub node_id: NodeId,
251 pub old_mode: u32,
253 pub new_mode: u32,
255}
256
257impl ChangePerm {
258 pub fn validate(&self) -> Result<()> {
260 if self.node_id.is_zero() {
261 return Err(PrikkError::CanonicalEncoding(
262 "ChangePerm node_id must be nonzero".to_string(),
263 ));
264 }
265 Ok(())
266 }
267}
268
269impl CanonicalEncode for ChangePerm {
270 fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
271 self.validate()?;
272 writer.field_bytes(1, self.node_id.as_bytes())?;
273 writer.field_u32(2, self.old_mode)?;
274 writer.field_u32(3, self.new_mode)?;
275 Ok(())
276 }
277}
278
279#[derive(Debug, Clone, PartialEq, Eq)]
282pub struct CreateSymlink {
283 pub path: String,
285 pub node_id: NodeId,
287 pub target: String,
290}
291
292impl CreateSymlink {
293 pub fn validate(&self) -> Result<()> {
297 if self.node_id.is_zero() {
298 return Err(PrikkError::CanonicalEncoding(
299 "CreateSymlink node_id must be nonzero".to_string(),
300 ));
301 }
302 validate_repo_path(&self.path)?;
303 Ok(())
304 }
305}
306
307impl CanonicalEncode for CreateSymlink {
308 fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
309 self.validate()?;
310 writer.field_repo_path(1, &self.path)?;
311 writer.field_bytes(2, self.node_id.as_bytes())?;
312 writer.field_string(3, &self.target)?;
313 Ok(())
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Eq)]
319pub struct ReplaceBinary {
320 pub node_id: NodeId,
322 pub old_blob_id: ObjectId,
324 pub new_blob_id: ObjectId,
326}
327
328impl ReplaceBinary {
329 pub fn validate(&self) -> Result<()> {
332 if self.node_id.is_zero() {
333 return Err(PrikkError::CanonicalEncoding(
334 "ReplaceBinary node_id must be nonzero".to_string(),
335 ));
336 }
337 Ok(())
338 }
339}
340
341impl CanonicalEncode for ReplaceBinary {
342 fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
343 self.validate()?;
344 writer.field_bytes(1, self.node_id.as_bytes())?;
345 writer.field_object_id(2, &self.old_blob_id)?;
346 writer.field_object_id(3, &self.new_blob_id)?;
347 Ok(())
348 }
349}