1use crate::content::Content;
4use crate::edge::Edge;
5use crate::id::{compute_content_hash, generate_block_id, BlockId};
6use crate::metadata::BlockMetadata;
7use crate::version::Version;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
24pub struct Block {
25 pub id: BlockId,
27
28 pub content: Content,
30
31 pub metadata: BlockMetadata,
33
34 #[serde(default, skip_serializing_if = "Vec::is_empty")]
36 pub edges: Vec<Edge>,
37
38 pub version: Version,
40}
41
42impl Block {
43 pub fn new(content: Content, semantic_role: Option<&str>) -> Self {
45 let id = generate_block_id(&content, semantic_role, None);
46 let content_hash = compute_content_hash(&content);
47 let mut metadata = BlockMetadata::new(content_hash);
48
49 if let Some(role) = semantic_role {
50 if let Some(parsed_role) = crate::metadata::SemanticRole::parse(role) {
51 metadata.semantic_role = Some(parsed_role);
52 }
53 }
54
55 Self {
56 id,
57 content,
58 metadata,
59 edges: Vec::new(),
60 version: Version::initial(),
61 }
62 }
63
64 pub fn with_id(id: BlockId, content: Content) -> Self {
66 let content_hash = compute_content_hash(&content);
67 Self {
68 id,
69 content,
70 metadata: BlockMetadata::new(content_hash),
71 edges: Vec::new(),
72 version: Version::initial(),
73 }
74 }
75
76 pub fn root() -> Self {
78 Self {
79 id: BlockId::root(),
80 content: Content::text(""),
81 metadata: BlockMetadata::default(),
82 edges: Vec::new(),
83 version: Version::initial(),
84 }
85 }
86
87 pub fn with_metadata(mut self, metadata: BlockMetadata) -> Self {
89 self.metadata = metadata;
90 self
91 }
92
93 pub fn with_label(mut self, label: impl Into<String>) -> Self {
95 self.metadata.label = Some(label.into());
96 self
97 }
98
99 pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
101 self.metadata.tags.push(tag.into());
102 self
103 }
104
105 pub fn with_edge(mut self, edge: Edge) -> Self {
107 self.edges.push(edge);
108 self
109 }
110
111 pub fn with_edges(mut self, edges: impl IntoIterator<Item = Edge>) -> Self {
113 self.edges.extend(edges);
114 self
115 }
116
117 pub fn content_type(&self) -> &'static str {
119 self.content.type_tag()
120 }
121
122 pub fn is_root(&self) -> bool {
124 self.id.is_root()
125 }
126
127 pub fn token_estimate(&self) -> crate::metadata::TokenEstimate {
129 self.metadata
130 .token_estimate
131 .unwrap_or_else(|| crate::metadata::TokenEstimate::compute(&self.content))
132 }
133
134 pub fn size_bytes(&self) -> usize {
136 self.content.size_bytes()
137 }
138
139 pub fn update_content(&mut self, content: Content, semantic_role: Option<&str>) {
141 self.content = content;
142 self.id = generate_block_id(&self.content, semantic_role, None);
143 self.metadata.content_hash = compute_content_hash(&self.content);
144 self.metadata.touch();
145 self.version.increment();
146 }
147
148 pub fn add_edge(&mut self, edge: Edge) {
150 self.edges.push(edge);
151 self.version.increment();
152 }
153
154 pub fn remove_edge(&mut self, target: &BlockId, edge_type: &crate::edge::EdgeType) -> bool {
156 let len_before = self.edges.len();
157 self.edges
158 .retain(|e| !(&e.target == target && &e.edge_type == edge_type));
159 let removed = self.edges.len() < len_before;
160 if removed {
161 self.version.increment();
162 }
163 removed
164 }
165
166 pub fn edges_of_type(&self, edge_type: &crate::edge::EdgeType) -> Vec<&Edge> {
168 self.edges
169 .iter()
170 .filter(|e| &e.edge_type == edge_type)
171 .collect()
172 }
173
174 pub fn has_tag(&self, tag: &str) -> bool {
176 self.metadata.has_tag(tag)
177 }
178}
179
180#[derive(Debug, Clone, Copy, PartialEq, Eq)]
182pub enum BlockState {
183 Live,
185 Orphaned,
187 Deleted,
189}
190
191impl std::fmt::Display for BlockState {
192 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193 match self {
194 BlockState::Live => write!(f, "live"),
195 BlockState::Orphaned => write!(f, "orphaned"),
196 BlockState::Deleted => write!(f, "deleted"),
197 }
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204 use crate::edge::EdgeType;
205
206 #[test]
207 fn test_block_creation() {
208 let block = Block::new(Content::text("Hello, world!"), Some("intro"));
209
210 assert!(!block.id.is_root());
211 assert_eq!(block.content_type(), "text");
212 assert!(block.edges.is_empty());
213 }
214
215 #[test]
216 fn test_deterministic_id() {
217 let block1 = Block::new(Content::text("Hello"), Some("intro"));
218 let block2 = Block::new(Content::text("Hello"), Some("intro"));
219
220 assert_eq!(block1.id, block2.id);
221 }
222
223 #[test]
224 fn test_different_role_different_id() {
225 let block1 = Block::new(Content::text("Hello"), Some("intro"));
226 let block2 = Block::new(Content::text("Hello"), Some("conclusion"));
227
228 assert_ne!(block1.id, block2.id);
229 }
230
231 #[test]
232 fn test_root_block() {
233 let root = Block::root();
234 assert!(root.is_root());
235 }
236
237 #[test]
238 fn test_block_builder() {
239 let block = Block::new(Content::text("Test"), None)
240 .with_label("Test Block")
241 .with_tag("important")
242 .with_tag("draft");
243
244 assert_eq!(block.metadata.label, Some("Test Block".to_string()));
245 assert!(block.has_tag("important"));
246 assert!(block.has_tag("draft"));
247 }
248
249 #[test]
250 fn test_block_edges() {
251 let target_id = BlockId::from_bytes([1u8; 12]);
252 let edge = Edge::new(EdgeType::References, target_id);
253
254 let mut block = Block::new(Content::text("Test"), None);
255 block.add_edge(edge);
256
257 assert_eq!(block.edges.len(), 1);
258 assert_eq!(block.edges_of_type(&EdgeType::References).len(), 1);
259
260 block.remove_edge(&target_id, &EdgeType::References);
261 assert!(block.edges.is_empty());
262 }
263
264 #[test]
265 fn test_update_content() {
266 let mut block = Block::new(Content::text("Original"), Some("intro"));
267 let original_id = block.id;
268 let original_version = block.version.counter;
269
270 block.update_content(Content::text("Updated"), Some("intro"));
271
272 assert_ne!(block.id, original_id);
273 assert!(block.version.counter > original_version);
274 }
275}