use prikk_error::{PrikkError, Result};
use prikk_object::{NodeKind, text_span_hash};
use crate::path::RepoPath;
use super::DecodedOperationKind;
use super::tlv::TlvCursor;
pub(super) fn decode_create_file(bytes: &[u8]) -> Result<DecodedOperationKind> {
let mut cursor = TlvCursor::new(bytes);
let mut path = None;
let mut node_id = None;
let mut blob_id = None;
let mut mode = None;
while let Some(field) = cursor.next_field()? {
match field.tag {
1 => path = Some(field.read_repo_path()?),
2 => node_id = Some(field.read_node_id()?),
3 => blob_id = Some(field.read_object_id_typed()?),
4 => mode = Some(field.read_u32()?),
other => {
return Err(PrikkError::MalformedData(format!(
"unknown CreateFile field tag: {other}"
)));
}
}
}
let path =
path.ok_or_else(|| PrikkError::MalformedData("CreateFile missing path".to_string()))?;
RepoPath::parse(&path)?;
let node_id = node_id
.ok_or_else(|| PrikkError::MalformedData("CreateFile missing node_id".to_string()))?;
let blob_id = blob_id
.ok_or_else(|| PrikkError::MalformedData("CreateFile missing blob_id".to_string()))?;
let mode =
mode.ok_or_else(|| PrikkError::MalformedData("CreateFile missing mode".to_string()))?;
Ok(DecodedOperationKind::CreateFile {
path,
node_id,
blob_id,
mode,
})
}
pub(super) fn decode_delete_node(bytes: &[u8]) -> Result<DecodedOperationKind> {
let mut cursor = TlvCursor::new(bytes);
let mut path = None;
let mut node_id = None;
let mut old_node_kind = None;
let mut old_blob_id = None;
let mut old_target = None;
let mut old_mode = None;
while let Some(field) = cursor.next_field()? {
match field.tag {
1 => path = Some(field.read_repo_path()?),
2 => node_id = Some(field.read_node_id()?),
3 => old_node_kind = Some(field.read_node_kind()?),
4 => old_blob_id = Some(field.read_object_id_typed()?),
5 => old_target = Some(field.read_string()?),
6 => old_mode = Some(field.read_u32()?),
other => {
return Err(PrikkError::MalformedData(format!(
"unknown DeleteNode field tag: {other}"
)));
}
}
}
let path =
path.ok_or_else(|| PrikkError::MalformedData("DeleteNode missing path".to_string()))?;
RepoPath::parse(&path)?;
let node_id = node_id
.ok_or_else(|| PrikkError::MalformedData("DeleteNode missing node_id".to_string()))?;
let old_node_kind = old_node_kind
.ok_or_else(|| PrikkError::MalformedData("DeleteNode missing old_node_kind".to_string()))?;
match old_node_kind {
NodeKind::TextFile | NodeKind::BinaryFile => {
if old_target.is_some() {
return Err(PrikkError::MalformedData(
"DeleteNode file kind must not carry old_target".to_string(),
));
}
let old_blob_id = old_blob_id.ok_or_else(|| {
PrikkError::MalformedData("DeleteNode file kind missing old_blob_id".to_string())
})?;
let old_mode = old_mode.ok_or_else(|| {
PrikkError::MalformedData("DeleteNode file kind missing old_mode".to_string())
})?;
Ok(DecodedOperationKind::DeleteNode {
path,
node_id,
preimage: super::DecodedDeletePreimage::File {
old_node_kind,
old_blob_id,
old_mode,
},
})
}
NodeKind::Symlink => {
if old_blob_id.is_some() || old_mode.is_some() {
return Err(PrikkError::MalformedData(
"DeleteNode symlink kind must not carry old_blob_id/old_mode".to_string(),
));
}
let old_target = old_target.ok_or_else(|| {
PrikkError::MalformedData("DeleteNode symlink kind missing old_target".to_string())
})?;
Ok(DecodedOperationKind::DeleteNode {
path,
node_id,
preimage: super::DecodedDeletePreimage::Symlink { old_target },
})
}
}
}
pub(super) fn decode_edit_text(bytes: &[u8]) -> Result<DecodedOperationKind> {
let mut cursor = TlvCursor::new(bytes);
let mut node_id = None;
let mut span_id = None;
let mut old_span_hash = None;
let mut left_anchor_hash = None;
let mut right_anchor_hash = None;
let mut replacement_text = None;
let mut old_span_text = None;
while let Some(field) = cursor.next_field()? {
match field.tag {
1 => node_id = Some(field.read_node_id()?),
2 => span_id = Some(field.read_span_hash()?),
3 => old_span_hash = Some(field.read_span_hash()?),
4 => left_anchor_hash = Some(field.read_span_hash()?),
5 => right_anchor_hash = Some(field.read_span_hash()?),
6 => replacement_text = Some(field.read_bytes_vec()?),
7 | 8 => {
let _ = field.read_u32()?; }
9 => old_span_text = Some(field.read_bytes_vec()?),
other => {
return Err(PrikkError::MalformedData(format!(
"unknown EditText field tag: {other}"
)));
}
}
}
let node_id =
node_id.ok_or_else(|| PrikkError::MalformedData("EditText missing node_id".to_string()))?;
let span_id =
span_id.ok_or_else(|| PrikkError::MalformedData("EditText missing span_id".to_string()))?;
let left_anchor_hash = left_anchor_hash.ok_or_else(|| {
PrikkError::MalformedData("EditText missing left_anchor_hash".to_string())
})?;
let right_anchor_hash = right_anchor_hash.ok_or_else(|| {
PrikkError::MalformedData("EditText missing right_anchor_hash".to_string())
})?;
let old_span_hash = old_span_hash
.ok_or_else(|| PrikkError::MalformedData("EditText missing old_span_hash".to_string()))?;
let replacement_text = replacement_text.ok_or_else(|| {
PrikkError::MalformedData("EditText missing replacement_text".to_string())
})?;
let old_span_text = old_span_text
.ok_or_else(|| PrikkError::MalformedData("EditText missing old_span_text".to_string()))?;
if old_span_hash != text_span_hash(&old_span_text) {
return Err(PrikkError::MalformedData(
"EditText old_span_hash != SHA-256(old_span_text)".to_string(),
));
}
if core::str::from_utf8(&old_span_text).is_err() {
return Err(PrikkError::MalformedData(
"EditText old_span_text is not well-formed UTF-8".to_string(),
));
}
if core::str::from_utf8(&replacement_text).is_err() {
return Err(PrikkError::MalformedData(
"EditText replacement_text is not well-formed UTF-8".to_string(),
));
}
Ok(DecodedOperationKind::EditText {
node_id,
span_id,
old_span_hash,
left_anchor_hash,
right_anchor_hash,
replacement_text,
old_span_text,
})
}
pub(super) fn decode_rename_path(bytes: &[u8]) -> Result<DecodedOperationKind> {
let mut cursor = TlvCursor::new(bytes);
let mut node_id = None;
let mut old_path = None;
let mut new_path = None;
while let Some(field) = cursor.next_field()? {
match field.tag {
1 => node_id = Some(field.read_node_id()?),
2 => old_path = Some(field.read_repo_path()?),
3 => new_path = Some(field.read_repo_path()?),
other => {
return Err(PrikkError::MalformedData(format!(
"unknown RenamePath field tag: {other}"
)));
}
}
}
let node_id = node_id
.ok_or_else(|| PrikkError::MalformedData("RenamePath missing node_id".to_string()))?;
let old_path = old_path
.ok_or_else(|| PrikkError::MalformedData("RenamePath missing old_path".to_string()))?;
let new_path = new_path
.ok_or_else(|| PrikkError::MalformedData("RenamePath missing new_path".to_string()))?;
RepoPath::parse(&old_path)?;
RepoPath::parse(&new_path)?;
Ok(DecodedOperationKind::RenamePath {
node_id,
old_path,
new_path,
})
}
pub(super) fn decode_change_perm(bytes: &[u8]) -> Result<DecodedOperationKind> {
let mut cursor = TlvCursor::new(bytes);
let mut node_id = None;
let mut old_mode = None;
let mut new_mode = None;
while let Some(field) = cursor.next_field()? {
match field.tag {
1 => node_id = Some(field.read_node_id()?),
2 => old_mode = Some(field.read_u32()?),
3 => new_mode = Some(field.read_u32()?),
other => {
return Err(PrikkError::MalformedData(format!(
"unknown ChangePerm field tag: {other}"
)));
}
}
}
let node_id = node_id
.ok_or_else(|| PrikkError::MalformedData("ChangePerm missing node_id".to_string()))?;
let old_mode = old_mode
.ok_or_else(|| PrikkError::MalformedData("ChangePerm missing old_mode".to_string()))?;
let new_mode = new_mode
.ok_or_else(|| PrikkError::MalformedData("ChangePerm missing new_mode".to_string()))?;
Ok(DecodedOperationKind::ChangePerm {
node_id,
old_mode,
new_mode,
})
}
pub(super) fn decode_create_symlink(bytes: &[u8]) -> Result<DecodedOperationKind> {
let mut cursor = TlvCursor::new(bytes);
let mut path = None;
let mut node_id = None;
let mut target = None;
while let Some(field) = cursor.next_field()? {
match field.tag {
1 => path = Some(field.read_repo_path()?),
2 => node_id = Some(field.read_node_id()?),
3 => target = Some(field.read_string()?),
other => {
return Err(PrikkError::MalformedData(format!(
"unknown CreateSymlink field tag: {other}"
)));
}
}
}
let path =
path.ok_or_else(|| PrikkError::MalformedData("CreateSymlink missing path".to_string()))?;
let node_id = node_id
.ok_or_else(|| PrikkError::MalformedData("CreateSymlink missing node_id".to_string()))?;
let target = target
.ok_or_else(|| PrikkError::MalformedData("CreateSymlink missing target".to_string()))?;
RepoPath::parse(&path)?;
Ok(DecodedOperationKind::CreateSymlink {
path,
node_id,
target,
})
}
pub(super) fn decode_replace_binary(bytes: &[u8]) -> Result<DecodedOperationKind> {
let mut cursor = TlvCursor::new(bytes);
let mut node_id = None;
let mut old_blob_id = None;
let mut new_blob_id = None;
while let Some(field) = cursor.next_field()? {
match field.tag {
1 => node_id = Some(field.read_node_id()?),
2 => old_blob_id = Some(field.read_object_id_typed()?),
3 => new_blob_id = Some(field.read_object_id_typed()?),
other => {
return Err(PrikkError::MalformedData(format!(
"unknown ReplaceBinary field tag: {other}"
)));
}
}
}
let node_id = node_id
.ok_or_else(|| PrikkError::MalformedData("ReplaceBinary missing node_id".to_string()))?;
let old_blob_id = old_blob_id.ok_or_else(|| {
PrikkError::MalformedData("ReplaceBinary missing old_blob_id".to_string())
})?;
let new_blob_id = new_blob_id.ok_or_else(|| {
PrikkError::MalformedData("ReplaceBinary missing new_blob_id".to_string())
})?;
Ok(DecodedOperationKind::ReplaceBinary {
node_id,
old_blob_id,
new_blob_id,
})
}