Streaming Tree ARchive format

clippy

+91 -72
+22 -2
src/error.rs
··· 1 + use cid::Cid; 1 2 use std::io; 2 3 use thiserror::Error; 4 + 5 + #[derive(Debug)] 6 + pub enum VerificationKind { 7 + Node, 8 + Record { key: Vec<u8> }, 9 + } 3 10 4 11 #[derive(Error, Debug)] 5 12 pub enum StarError { ··· 15 22 InvalidHeader, 16 23 #[error("Unexpected EOF")] 17 24 UnexpectedEof, 18 - #[error("Verification failed: expected {expected}, got {computed}")] 19 - VerificationFailed { expected: String, computed: String }, 25 + 26 + #[error("Verification failed for {kind:?}: expected {expected}, got {computed}")] 27 + VerificationFailed { 28 + kind: VerificationKind, 29 + expected: Box<Cid>, 30 + computed: Box<Cid>, 31 + }, 32 + 20 33 #[error("Invalid state: {0}")] 21 34 InvalidState(String), 35 + 36 + #[error("Invalid structure: {0}")] 37 + InvalidStructure(String), 38 + 39 + #[error("Height mismatch: found {found}, expected {expected}")] 40 + HeightMismatch { found: u32, expected: u32 }, 41 + 22 42 #[error("Trailing data")] 23 43 TrailingData, 24 44 }
+43 -43
src/parser.rs
··· 1 - use crate::error::{Result, StarError}; 1 + use crate::error::{Result, StarError, VerificationKind}; 2 2 use crate::types::{StarCommit, StarItem, StarMstNode}; 3 3 use crate::validation::validate_node_structure; 4 4 use cid::Cid; 5 5 use sha2::{Digest, Sha256}; 6 6 7 - #[derive(Debug)] 7 + #[derive(Debug, Default)] 8 8 enum State { 9 + #[default] 9 10 Header, 10 11 Body { 11 12 stack: Vec<StackItem>, ··· 29 30 node: StarMstNode, 30 31 parent_expected: Option<Cid>, 31 32 pending_records: Vec<(usize, Cid)>, 32 - height: u32, 33 33 }, 34 34 } 35 35 36 + #[derive(Default)] 36 37 pub struct StarParser { 37 38 state: State, 38 39 } ··· 114 115 } 115 116 116 117 fn parse_header(&mut self, buf: &[u8]) -> Result<Option<(usize, StarItem)>> { 117 - if buf.len() < 1 { 118 + if buf.is_empty() { 118 119 return Ok(None); 119 120 } 120 121 if buf[0] != 0x2A { ··· 165 166 } 166 167 167 168 fn process_verification(stack: &mut Vec<StackItem>) -> Result<bool> { 168 - if let Some(StackItem::VerifyLayer0 { .. }) = stack.last() { 169 - if let Some(StackItem::VerifyLayer0 { 169 + if let Some(StackItem::VerifyLayer0 { .. }) = stack.last() 170 + && let Some(StackItem::VerifyLayer0 { 170 171 mut node, 171 172 parent_expected, 172 173 pending_records, 173 174 .. 174 175 }) = stack.pop() 175 - { 176 - for (idx, cid) in pending_records { 177 - if idx < node.e.len() { 178 - node.e[idx].v = Some(cid); 179 - } 176 + { 177 + for (idx, cid) in pending_records { 178 + if idx < node.e.len() { 179 + node.e[idx].v = Some(cid); 180 180 } 181 + } 181 182 182 - let repo_node = node.to_repo()?; 183 - let bytes = serde_ipld_dagcbor::to_vec(&repo_node) 184 - .map_err(|e| StarError::Cbor(e.to_string()))?; 183 + let repo_node = node.to_repo()?; 184 + let bytes = serde_ipld_dagcbor::to_vec(&repo_node) 185 + .map_err(|e| StarError::Cbor(e.to_string()))?; 185 186 186 - let hash = Sha256::digest(&bytes); 187 - let cid = Cid::new_v1(0x71, cid::multihash::Multihash::wrap(0x12, &hash)?); 187 + let hash = Sha256::digest(&bytes); 188 + let cid = Cid::new_v1(0x71, cid::multihash::Multihash::wrap(0x12, &hash)?); 188 189 189 - if let Some(expected) = parent_expected { 190 - if cid != expected { 191 - return Err(StarError::VerificationFailed { 192 - expected: expected.to_string(), 193 - computed: cid.to_string(), 194 - }); 195 - } 196 - } 197 - return Ok(true); 190 + if let Some(expected) = parent_expected 191 + && cid != expected 192 + { 193 + return Err(StarError::VerificationFailed { 194 + kind: VerificationKind::Node, 195 + expected: Box::new(expected), 196 + computed: Box::new(cid), 197 + }); 198 198 } 199 + return Ok(true); 199 200 } 200 201 Ok(false) 201 202 } ··· 230 231 231 232 // Check for implicit records (needed for VerifyLayer0 logic) 232 233 let mut has_implicit = false; 233 - // Optimization: validate_node_structure already ensures consistency of v based on height. 234 - // If height == 0, v is None (Implicit). If height > 0, v is Some (Explicit). 235 234 if height == 0 { 236 235 for e in &node.e { 237 236 if e.v_archived == Some(true) { ··· 248 247 249 248 let hash = Sha256::digest(&bytes); 250 249 let cid = Cid::new_v1(0x71, cid::multihash::Multihash::wrap(0x12, &hash)?); 251 - if let Some(exp) = expected { 252 - if cid != exp { 253 - return Err(StarError::VerificationFailed { 254 - expected: exp.to_string(), 255 - computed: cid.to_string(), 256 - }); 257 - } 250 + if let Some(exp) = expected 251 + && cid != exp 252 + { 253 + return Err(StarError::VerificationFailed { 254 + kind: VerificationKind::Node, 255 + expected: Box::new(exp), 256 + computed: Box::new(cid), 257 + }); 258 258 } 259 259 } else { 260 260 stack.push(StackItem::VerifyLayer0 { 261 261 node: node.clone(), 262 262 parent_expected: expected, 263 263 pending_records: Vec::new(), 264 - height, 265 264 }); 266 265 } 267 266 ··· 323 322 key: Vec<u8>, 324 323 expected: Option<Cid>, 325 324 implicit_index: Option<usize>, 326 - stack: &mut Vec<StackItem>, 325 + stack: &mut [StackItem], 327 326 ) -> Result<Option<StarItem>> { 328 327 let hash = Sha256::digest(block_bytes); 329 328 let cid = Cid::new_v1(0x71, cid::multihash::Multihash::wrap(0x12, &hash)?); 330 329 331 - if let Some(exp) = expected { 332 - if cid != exp { 333 - return Err(StarError::VerificationFailed { 334 - expected: exp.to_string(), 335 - computed: cid.to_string(), 336 - }); 337 - } 330 + if let Some(exp) = expected 331 + && cid != exp 332 + { 333 + return Err(StarError::VerificationFailed { 334 + kind: VerificationKind::Record { key: key.clone() }, 335 + expected: Box::new(exp), 336 + computed: Box::new(cid), 337 + }); 338 338 } 339 339 340 340 if let Some(idx) = implicit_index {
+6 -5
src/ser.rs
··· 111 111 112 112 // Validator State Machine (Moved from validation.rs) 113 113 114 - #[derive(Debug)] 114 + #[derive(Debug, Default)] 115 115 pub struct StarValidator { 116 116 state: ValidatorState, 117 117 } 118 118 119 - #[derive(Debug)] 119 + #[derive(Debug, Default)] 120 120 enum ValidatorState { 121 + #[default] 121 122 Header, 122 - Body { stack: Vec<Expectation> }, 123 - Done, 123 + Body { 124 + stack: Vec<Expectation>, 125 + }, 124 126 } 125 127 126 128 #[derive(Debug)] ··· 220 222 pub fn is_done(&self) -> bool { 221 223 match &self.state { 222 224 ValidatorState::Body { stack } => stack.is_empty(), 223 - ValidatorState::Done => true, 224 225 _ => false, 225 226 } 226 227 }
+6 -10
src/tests.rs
··· 17 17 #[test] 18 18 fn test_roundtrip_basic() { 19 19 // 1. Create a dummy record 20 + // Use "bar" (sha256 starts with fc... -> 0 leading zeros -> height 0) 20 21 let record_data = b"hello world"; 21 22 let record_cid = create_test_cid(record_data); 22 - let key = b"bar".to_vec(); // Height 0 23 + let key = b"bar".to_vec(); 23 24 24 25 // 2. Create a dummy MST Node (Layer 0, implicit record) 25 26 let node_entry = StarMstEntry { ··· 191 192 let mut buf = Vec::new(); 192 193 let mut serializer = StarSerializer::new(&mut buf); 193 194 194 - // 1. Create invalid node (height 0 but empty) 195 + // 1. Create invalid node (root empty) 195 196 let invalid_node = StarMstNode { 196 197 l: None, 197 198 l_archived: None, ··· 215 216 // Node Fail 216 217 let err = serializer.write_node(&invalid_node).unwrap_err(); 217 218 match err { 218 - crate::error::StarError::InvalidState(msg) => { 219 - assert!( 220 - msg.contains("Root cannot be empty") 221 - || msg.contains("Height 0 cannot be empty"), 222 - "Got message: {}", 223 - msg 224 - ); 219 + crate::error::StarError::InvalidStructure(msg) => { 220 + assert!(msg.contains("Root cannot be empty"), "Got message: {}", msg); 225 221 } 226 - e => panic!("Expected InvalidState, got {:?}", e), 222 + e => panic!("Expected InvalidStructure, got {:?}", e), 227 223 } 228 224 } 229 225 }
+14 -12
src/validation.rs
··· 30 30 31 31 if let Some(existing) = node_height { 32 32 if h != existing { 33 - return Err(StarError::InvalidState(format!( 33 + return Err(StarError::InvalidStructure(format!( 34 34 "Inconsistent key height in node: {} vs {}", 35 35 h, existing 36 36 ))); ··· 46 46 let height = match (node_height, expected) { 47 47 (Some(h), Some(exp)) => { 48 48 if h != exp { 49 - return Err(StarError::InvalidState(format!( 50 - "Height mismatch: found {}, expected {}", 51 - h, exp 52 - ))); 49 + return Err(StarError::HeightMismatch { 50 + found: h, 51 + expected: exp, 52 + }); 53 53 } 54 54 h 55 55 } 56 56 (Some(h), None) => h, 57 57 (None, Some(exp)) => { 58 58 if exp == 0 { 59 - return Err(StarError::InvalidState("Height 0 cannot be empty".into())); 59 + return Err(StarError::InvalidStructure( 60 + "Height 0 cannot be empty".into(), 61 + )); 60 62 } 61 63 exp 62 64 } 63 - (None, None) => return Err(StarError::InvalidState("Root cannot be empty".into())), 65 + (None, None) => return Err(StarError::InvalidStructure("Root cannot be empty".into())), 64 66 }; 65 67 66 68 if height == 0 { 67 69 if node.l.is_some() || node.l_archived.is_some() { 68 - return Err(StarError::InvalidState( 70 + return Err(StarError::InvalidStructure( 69 71 "Height 0 node cannot have left child".into(), 70 72 )); 71 73 } 72 74 for e in &node.e { 73 75 if e.t.is_some() || e.t_archived.is_some() { 74 - return Err(StarError::InvalidState( 76 + return Err(StarError::InvalidStructure( 75 77 "Height 0 entries cannot have subtrees".into(), 76 78 )); 77 79 } 78 80 if e.v.is_some() { 79 - return Err(StarError::InvalidState( 81 + return Err(StarError::InvalidStructure( 80 82 "Height 0 node must omit record CIDs".into(), 81 83 )); 82 84 } 83 85 } 84 86 } else { 85 87 if node.e.is_empty() && node.l.is_none() { 86 - return Err(StarError::InvalidState( 88 + return Err(StarError::InvalidStructure( 87 89 "Empty intermediate node must have left child".into(), 88 90 )); 89 91 } 90 92 for e in &node.e { 91 93 if e.v.is_none() { 92 - return Err(StarError::InvalidState( 94 + return Err(StarError::InvalidStructure( 93 95 "Intermediate node must include record CIDs".into(), 94 96 )); 95 97 }