use prikk_error::{PrikkError, Result};
use crate::path::validate_repo_path;
use crate::payload::node::{NodeId, NodeKind};
use crate::{CanonicalEncode, CanonicalWriter, ObjectId};
use super::{TEXT_SPAN_HASH_BYTES, text_span_hash};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreateFile {
pub path: String,
pub node_id: NodeId,
pub blob_id: ObjectId,
pub mode: u32,
}
impl CreateFile {
pub fn validate(&self) -> Result<()> {
if self.node_id.is_zero() {
return Err(PrikkError::CanonicalEncoding(
"CreateFile node_id must be nonzero".to_string(),
));
}
validate_repo_path(&self.path)?;
Ok(())
}
}
impl CanonicalEncode for CreateFile {
fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
self.validate()?;
writer.field_repo_path(1, &self.path)?;
writer.field_bytes(2, self.node_id.as_bytes())?;
writer.field_object_id(3, &self.blob_id)?;
writer.field_u32(4, self.mode)?;
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DeleteNodePreimage {
File {
old_blob_id: ObjectId,
old_mode: u32,
},
Symlink {
old_target: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DeleteNode {
pub path: String,
pub node_id: NodeId,
pub old_node_kind: NodeKind,
pub preimage: DeleteNodePreimage,
}
impl DeleteNode {
pub fn validate(&self) -> Result<()> {
if self.node_id.is_zero() {
return Err(PrikkError::CanonicalEncoding(
"DeleteNode node_id must be nonzero".to_string(),
));
}
validate_repo_path(&self.path)?;
let consistent = matches!(
(self.old_node_kind, &self.preimage),
(
NodeKind::TextFile | NodeKind::BinaryFile,
DeleteNodePreimage::File { .. }
) | (NodeKind::Symlink, DeleteNodePreimage::Symlink { .. })
);
if !consistent {
return Err(PrikkError::CanonicalEncoding(
"DeleteNode old_node_kind does not match preimage discriminator".to_string(),
));
}
Ok(())
}
}
impl CanonicalEncode for DeleteNode {
fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
self.validate()?;
writer.field_repo_path(1, &self.path)?;
writer.field_bytes(2, self.node_id.as_bytes())?;
writer.field_enum_u16(3, self.old_node_kind.code())?;
match &self.preimage {
DeleteNodePreimage::File {
old_blob_id,
old_mode,
} => {
writer.field_object_id(4, old_blob_id)?;
writer.field_u32(6, *old_mode)?;
}
DeleteNodePreimage::Symlink { old_target } => {
writer.field_string(5, old_target)?;
}
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EditText {
pub node_id: NodeId,
pub span_id: [u8; TEXT_SPAN_HASH_BYTES],
pub old_span_hash: [u8; TEXT_SPAN_HASH_BYTES],
pub left_anchor_hash: [u8; TEXT_SPAN_HASH_BYTES],
pub right_anchor_hash: [u8; TEXT_SPAN_HASH_BYTES],
pub replacement_text: Vec<u8>,
pub presentation_hint_line: Option<u32>,
pub presentation_hint_column: Option<u32>,
pub old_span_text: Vec<u8>,
}
impl EditText {
pub fn validate(&self) -> Result<()> {
if self.node_id.is_zero() {
return Err(PrikkError::CanonicalEncoding(
"EditText node_id must be nonzero".to_string(),
));
}
if self.old_span_hash != text_span_hash(&self.old_span_text) {
return Err(PrikkError::CanonicalEncoding(
"EditText old_span_hash must equal SHA-256(old_span_text)".to_string(),
));
}
if core::str::from_utf8(&self.old_span_text).is_err() {
return Err(PrikkError::CanonicalEncoding(
"EditText old_span_text must be well-formed UTF-8".to_string(),
));
}
if core::str::from_utf8(&self.replacement_text).is_err() {
return Err(PrikkError::CanonicalEncoding(
"EditText replacement_text must be well-formed UTF-8".to_string(),
));
}
Ok(())
}
}
impl CanonicalEncode for EditText {
fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
self.validate()?;
writer.field_bytes(1, self.node_id.as_bytes())?;
writer.field_bytes(2, &self.span_id)?;
writer.field_bytes(3, &self.old_span_hash)?;
writer.field_bytes(4, &self.left_anchor_hash)?;
writer.field_bytes(5, &self.right_anchor_hash)?;
writer.field_bytes(6, &self.replacement_text)?;
if let Some(line) = self.presentation_hint_line {
writer.field_u32(7, line)?;
}
if let Some(column) = self.presentation_hint_column {
writer.field_u32(8, column)?;
}
writer.field_bytes(9, &self.old_span_text)?;
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RenamePath {
pub node_id: NodeId,
pub old_path: String,
pub new_path: String,
}
impl RenamePath {
pub fn validate(&self) -> Result<()> {
if self.node_id.is_zero() {
return Err(PrikkError::CanonicalEncoding(
"RenamePath node_id must be nonzero".to_string(),
));
}
validate_repo_path(&self.old_path)?;
validate_repo_path(&self.new_path)?;
Ok(())
}
}
impl CanonicalEncode for RenamePath {
fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
self.validate()?;
writer.field_bytes(1, self.node_id.as_bytes())?;
writer.field_repo_path(2, &self.old_path)?;
writer.field_repo_path(3, &self.new_path)?;
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ChangePerm {
pub node_id: NodeId,
pub old_mode: u32,
pub new_mode: u32,
}
impl ChangePerm {
pub fn validate(&self) -> Result<()> {
if self.node_id.is_zero() {
return Err(PrikkError::CanonicalEncoding(
"ChangePerm node_id must be nonzero".to_string(),
));
}
Ok(())
}
}
impl CanonicalEncode for ChangePerm {
fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
self.validate()?;
writer.field_bytes(1, self.node_id.as_bytes())?;
writer.field_u32(2, self.old_mode)?;
writer.field_u32(3, self.new_mode)?;
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreateSymlink {
pub path: String,
pub node_id: NodeId,
pub target: String,
}
impl CreateSymlink {
pub fn validate(&self) -> Result<()> {
if self.node_id.is_zero() {
return Err(PrikkError::CanonicalEncoding(
"CreateSymlink node_id must be nonzero".to_string(),
));
}
validate_repo_path(&self.path)?;
Ok(())
}
}
impl CanonicalEncode for CreateSymlink {
fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
self.validate()?;
writer.field_repo_path(1, &self.path)?;
writer.field_bytes(2, self.node_id.as_bytes())?;
writer.field_string(3, &self.target)?;
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReplaceBinary {
pub node_id: NodeId,
pub old_blob_id: ObjectId,
pub new_blob_id: ObjectId,
}
impl ReplaceBinary {
pub fn validate(&self) -> Result<()> {
if self.node_id.is_zero() {
return Err(PrikkError::CanonicalEncoding(
"ReplaceBinary node_id must be nonzero".to_string(),
));
}
Ok(())
}
}
impl CanonicalEncode for ReplaceBinary {
fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
self.validate()?;
writer.field_bytes(1, self.node_id.as_bytes())?;
writer.field_object_id(2, &self.old_blob_id)?;
writer.field_object_id(3, &self.new_blob_id)?;
Ok(())
}
}