eidetica/entry/builder.rs
1//! Builder for creating Entry instances.
2
3use std::collections::HashSet;
4
5use rand::Rng;
6
7use super::{ENTRY_VERSION, Entry, EntryError, ID, RawData, SubTreeNode, TreeNode};
8use crate::{Result, auth::types::SigInfo, constants::ROOT, crdt::Doc, store::StoreError};
9
10/// A builder for creating `Entry` instances.
11///
12/// `EntryBuilder` allows mutable construction of an entry's content.
13/// Once finalized with the `build()` method, it produces an immutable `Entry`
14/// with a deterministically calculated ID.
15///
16/// # Parameter Type Efficiency
17///
18/// The builder uses `impl Into<ID>` and `impl Into<RawData>` for parameters, allowing you to pass
19/// string literals, `&str`, `String`, or the appropriate types without unnecessary conversions:
20///
21/// ```ignore
22/// // Efficient - no unnecessary .to_string() calls needed
23/// let entry = Entry::builder("root_id")
24/// .add_parent("parent1")
25/// .set_subtree_data("users", "user_data")
26/// .build();
27/// ```
28///
29/// # Mutable Construction
30///
31/// The builder provides two patterns for construction:
32/// 1. Ownership chaining: Each method returns `self` for chained calls.
33/// ```
34/// # use eidetica::Entry;
35/// # let root_id = "root_id".to_string();
36/// # let data = "data".to_string();
37/// let entry = Entry::builder(root_id)
38/// .set_subtree_data("users".to_string(), "user_data".to_string())
39/// .add_parent("parent_id".to_string())
40/// .build();
41/// ```
42///
43/// 2. Mutable reference: Methods ending in `_mut` modify the builder in place.
44/// ```
45/// # use eidetica::Entry;
46/// # let root_id = "root_id".to_string();
47/// # let data = "data".to_string();
48/// let mut builder = Entry::builder(root_id);
49/// builder.set_subtree_data_mut("users".to_string(), "user_data".to_string());
50/// builder.add_parent_mut("parent_id".to_string());
51/// let entry = builder.build();
52/// ```
53///
54/// # Example
55///
56/// ```
57/// use eidetica::Entry;
58///
59/// // Create a builder for a regular entry
60/// let entry = Entry::builder("root_id")
61/// .add_parent("parent1")
62/// .set_subtree_data("users", "user_data")
63/// .build();
64///
65/// // Create a builder for a top-level root entry
66/// let root_entry = Entry::root_builder()
67/// .set_subtree_data("users", "initial_user_data")
68/// .build();
69/// ```
70#[derive(Clone, Debug)]
71pub struct EntryBuilder {
72 pub(super) tree: TreeNode,
73 pub(super) subtrees: Vec<SubTreeNode>,
74 pub(super) sig: SigInfo,
75}
76
77impl EntryBuilder {
78 /// Creates a new `EntryBuilder` for an entry associated with a specific tree root.
79 ///
80 /// # Arguments
81 /// * `root` - The `ID` of the root `Entry` of the tree this entry will belong to.
82 ///
83 /// Note: It's generally preferred to use the static `Entry::builder()` method
84 /// instead of calling this constructor directly.
85 pub fn new(root: impl Into<ID>) -> Self {
86 Self {
87 tree: TreeNode {
88 root: root.into(),
89 parents: Vec::new(),
90 metadata: None,
91 height: 0,
92 },
93 subtrees: Vec::new(),
94 sig: SigInfo::default(),
95 }
96 }
97
98 /// Creates a new `EntryBuilder` for a top-level (root) entry for a new tree.
99 ///
100 /// Root entries have an empty string as their `root` ID and include a special ROOT subtree marker.
101 /// This method is typically used when creating a new tree.
102 ///
103 /// Note: It's generally preferred to use the static `Entry::root_builder()` method
104 /// instead of calling this constructor directly.
105 pub fn new_top_level() -> Self {
106 let mut builder = Self::new("");
107 // Add a special subtree that identifies this as a root entry
108 builder.set_subtree_data_mut(ROOT, "");
109
110 // Add random entropy to metadata to ensure unique IDs for each root entry
111 let entropy: u64 = rand::thread_rng().r#gen();
112 let metadata_json = format!(r#"{{"entropy":{entropy}}}"#);
113 builder.set_metadata_mut(&metadata_json);
114
115 builder
116 }
117
118 /// Set the authentication information for this entry.
119 ///
120 /// # Arguments
121 /// * `auth` - The authentication information including key ID and optional signature
122 pub fn set_sig(mut self, sig: SigInfo) -> Self {
123 self.sig = sig;
124 self
125 }
126
127 /// Mutable reference version of set_auth.
128 /// Set the authentication information for this entry.
129 ///
130 /// # Arguments
131 /// * `auth` - The authentication information including key ID and optional signature
132 pub fn set_sig_mut(&mut self, sig: SigInfo) -> &mut Self {
133 self.sig = sig;
134 self
135 }
136
137 /// Get the names of all subtrees this entry builder contains data for.
138 /// The names are returned in alphabetical order.
139 pub fn subtrees(&self) -> Vec<String> {
140 self.subtrees
141 .iter()
142 .map(|subtree| subtree.name.clone())
143 .collect()
144 }
145
146 /// Get the `RawData` for a specific named subtree within this entry builder.
147 ///
148 /// Returns an error if the subtree is not found or if the subtree exists but has no data (`None`).
149 pub fn data(&self, subtree_name: impl AsRef<str>) -> Result<&RawData> {
150 self.subtrees
151 .iter()
152 .find(|node| node.name == subtree_name.as_ref())
153 .and_then(|node| node.data.as_ref())
154 .ok_or_else(|| {
155 StoreError::KeyNotFound {
156 store: "entry".to_string(),
157 key: subtree_name.as_ref().to_string(),
158 }
159 .into()
160 })
161 }
162
163 /// Get the IDs of the parent entries for the main tree.
164 /// The parent IDs are returned in alphabetical order.
165 pub fn parents(&self) -> Result<Vec<ID>> {
166 Ok(self.tree.parents.clone())
167 }
168
169 /// Get the IDs of the parent entries specific to a named subtree's history.
170 /// The parent IDs are returned in alphabetical order.
171 pub fn subtree_parents(&self, subtree_name: impl AsRef<str>) -> Result<Vec<ID>> {
172 self.subtrees
173 .iter()
174 .find(|node| node.name == subtree_name.as_ref())
175 .map(|node| node.parents.clone())
176 .ok_or_else(|| {
177 StoreError::KeyNotFound {
178 store: "entry".to_string(),
179 key: subtree_name.as_ref().to_string(),
180 }
181 .into()
182 })
183 }
184
185 /// Sort a list of parent IDs in alphabetical order.
186 fn sort_parents_list(parents: &mut [ID]) {
187 parents.sort();
188 }
189
190 /// Sort the list of subtrees in alphabetical order by name.
191 ///
192 /// This is important for ensuring entries with the same content have the same ID.
193 fn sort_subtrees_list(&mut self) {
194 self.subtrees.sort_by(|a, b| a.name.cmp(&b.name));
195 }
196
197 /// Sets data for a named subtree, creating it if it doesn't exist.
198 /// The list of subtrees will be sorted by name when `build()` is called.
199 ///
200 /// # Arguments
201 /// * `name` - The name of the subtree (e.g., "users", "products").
202 /// * `data` - `RawData` (serialized string) specific to this entry for the named subtree.
203 pub fn set_subtree_data(mut self, name: impl Into<String>, data: impl Into<RawData>) -> Self {
204 let name = name.into();
205 if let Some(node) = self.subtrees.iter_mut().find(|node| node.name == name) {
206 node.data = Some(data.into());
207 } else {
208 self.subtrees.push(SubTreeNode {
209 name,
210 data: Some(data.into()),
211 parents: vec![],
212 height: None,
213 });
214 }
215 self
216 }
217
218 /// Mutable reference version of set_subtree_data.
219 /// Sets data for a named subtree, creating it if it doesn't exist.
220 /// The list of subtrees will be sorted by name when `build()` is called.
221 ///
222 /// # Arguments
223 /// * `name` - The name of the subtree (e.g., "users", "products").
224 /// * `data` - `RawData` (serialized string) specific to this entry for the named subtree.
225 pub fn set_subtree_data_mut(
226 &mut self,
227 name: impl Into<String>,
228 data: impl Into<RawData>,
229 ) -> &mut Self {
230 let name = name.into();
231 if let Some(node) = self.subtrees.iter_mut().find(|node| node.name == name) {
232 node.data = Some(data.into());
233 } else {
234 self.subtrees.push(SubTreeNode {
235 name,
236 data: Some(data.into()),
237 parents: vec![],
238 height: None,
239 });
240 }
241 self
242 }
243
244 /// Removes subtrees that have empty data.
245 ///
246 /// This removes subtrees with `Some("")` (actual empty data) and subtrees with `None`
247 /// (no data changes) UNLESS the subtree is referenced in the `_index` subtree's data.
248 ///
249 /// When `_index` is updated for a subtree, that subtree must appear in the Entry.
250 /// This is marked by having `None` data and being referenced in `_index`.
251 ///
252 /// This is useful for cleaning up entries before building.
253 ///
254 /// # Errors
255 ///
256 /// Returns an error if the `_index` subtree data exists but cannot be deserialized.
257 pub fn remove_empty_subtrees(mut self) -> Result<Self> {
258 // Get the set of subtrees referenced in _index
259 let index_subtrees = self.get_index_referenced_subtrees()?;
260
261 self.subtrees.retain(|subtree| match &subtree.data {
262 None => {
263 // Preserve None only if this subtree is referenced in _index
264 index_subtrees.contains(&subtree.name)
265 }
266 Some(d) => !d.is_empty(), // Remove only if Some with empty string
267 });
268 Ok(self)
269 }
270
271 /// Mutable reference version of remove_empty_subtrees.
272 ///
273 /// Removes subtrees with `Some("")` and subtrees with `None` unless referenced in `_index`.
274 ///
275 /// # Errors
276 ///
277 /// Returns an error if the `_index` subtree data exists but cannot be deserialized.
278 pub fn remove_empty_subtrees_mut(&mut self) -> Result<&mut Self> {
279 // Get the set of subtrees referenced in _index
280 let index_subtrees = self.get_index_referenced_subtrees()?;
281
282 self.subtrees.retain(|subtree| match &subtree.data {
283 None => {
284 // Preserve None only if this subtree is referenced in _index
285 index_subtrees.contains(&subtree.name)
286 }
287 Some(d) => !d.is_empty(), // Remove only if Some with empty string
288 });
289 Ok(self)
290 }
291
292 /// Get the set of subtree names that are referenced in the `_index` subtree's local data.
293 ///
294 /// Returns a set of subtree names that have entries in the local `_index` subtree.
295 /// This is used to determine which subtrees with `None` data should be preserved.
296 ///
297 /// # Errors
298 ///
299 /// Returns an error if the `_index` subtree data exists but cannot be deserialized.
300 fn get_index_referenced_subtrees(&self) -> Result<HashSet<String>> {
301 let mut result = HashSet::new();
302
303 // Find the _index subtree's local data
304 if let Some(index_node) = self.subtrees.iter().find(|node| node.name == "_index") {
305 // If _index has data, deserialize it and get all keys
306 if let Some(data) = &index_node.data {
307 // Try to deserialize as a Doc to get the keys
308 let doc = serde_json::from_str::<Doc>(data).map_err(|e| {
309 EntryError::InvalidIndexData {
310 reason: format!(
311 "Failed to deserialize _index subtree data: {} (data preview: {})",
312 e,
313 data.chars().take(100).collect::<String>()
314 ),
315 }
316 })?;
317
318 for key in doc.keys() {
319 result.insert(key.to_string());
320 }
321 }
322 }
323
324 Ok(result)
325 }
326
327 /// Set the root ID for this entry.
328 ///
329 /// # Arguments
330 /// * `root` - The ID of the root `Entry` of the tree this entry will belong to.
331 ///
332 /// # Returns
333 /// A mutable reference to self for method chaining.
334 pub fn set_root(mut self, root: impl Into<String>) -> Self {
335 self.tree.root = root.into().into();
336 self
337 }
338
339 /// Mutable reference version of set_root.
340 /// Set the root ID for this entry.
341 ///
342 /// # Arguments
343 /// * `root` - The ID of the root `Entry` of the tree this entry will belong to.
344 ///
345 /// # Returns
346 /// A mutable reference to self for method chaining.
347 pub fn set_root_mut(&mut self, root: impl Into<String>) -> &mut Self {
348 self.tree.root = root.into().into();
349 self
350 }
351
352 /// Set the parent IDs for the main tree history.
353 /// The provided vector will be sorted alphabetically during the `build()` process.
354 pub fn set_parents(mut self, parents: Vec<ID>) -> Self {
355 self.tree.parents = parents;
356 self
357 }
358
359 /// Mutable reference version of set_parents.
360 /// Set the parent IDs for the main tree history.
361 /// The provided vector will be sorted alphabetically during the `build()` process.
362 pub fn set_parents_mut(&mut self, parents: Vec<ID>) -> &mut Self {
363 self.tree.parents = parents;
364 self
365 }
366
367 /// Add a single parent ID to the main tree history.
368 /// Parents will be sorted and duplicates handled during the `build()` process.
369 pub fn add_parent(mut self, parent_id: impl Into<String>) -> Self {
370 self.tree.parents.push(parent_id.into().into());
371 self
372 }
373
374 /// Mutable reference version of add_parent.
375 /// Add a single parent ID to the main tree history.
376 /// Parents will be sorted and duplicates handled during the `build()` process.
377 pub fn add_parent_mut(&mut self, parent_id: impl Into<String>) -> &mut Self {
378 self.tree.parents.push(parent_id.into().into());
379 self
380 }
381
382 /// Get a reference to the current parent IDs for the main tree history.
383 pub fn get_parents(&self) -> Option<&Vec<ID>> {
384 if self.tree.parents.is_empty() {
385 None
386 } else {
387 Some(&self.tree.parents)
388 }
389 }
390
391 /// Set the parent IDs for a specific named subtree's history.
392 /// The provided vector will be sorted alphabetically and de-duplicated during the `build()` process.
393 /// If the subtree does not exist, it will be created with empty data ("{}").
394 /// The list of subtrees will be sorted by name when `build()` is called.
395 pub fn set_subtree_parents(
396 mut self,
397 subtree_name: impl Into<String>,
398 parents: Vec<ID>,
399 ) -> Self {
400 let subtree_name = subtree_name.into();
401 if let Some(node) = self
402 .subtrees
403 .iter_mut()
404 .find(|node| node.name == subtree_name)
405 {
406 node.parents = parents;
407 } else {
408 // Create new SubTreeNode if it doesn't exist, then set parents
409 self.subtrees.push(SubTreeNode {
410 name: subtree_name,
411 data: None,
412 parents,
413 height: None,
414 });
415 }
416 self
417 }
418
419 /// Mutable reference version of set_subtree_parents.
420 /// Set the parent IDs for a specific named subtree's history.
421 /// The provided vector will be sorted alphabetically and de-duplicated during the `build()` process.
422 /// If the subtree does not exist, it will be created with no data (`None`).
423 /// The list of subtrees will be sorted by name when `build()` is called.
424 pub fn set_subtree_parents_mut(
425 &mut self,
426 subtree_name: impl Into<String>,
427 parents: Vec<ID>,
428 ) -> &mut Self {
429 let subtree_name = subtree_name.into();
430 if let Some(node) = self
431 .subtrees
432 .iter_mut()
433 .find(|node| node.name == subtree_name)
434 {
435 node.parents = parents;
436 } else {
437 // Create new SubTreeNode if it doesn't exist, then set parents
438 self.subtrees.push(SubTreeNode {
439 name: subtree_name,
440 data: None,
441 parents,
442 height: None,
443 });
444 }
445 self
446 }
447
448 /// Add a single parent ID to a specific named subtree's history.
449 /// If the subtree does not exist, it will be created with no data (`None`).
450 /// Parent IDs will be sorted and de-duplicated during the `build()` process.
451 /// The list of subtrees will be sorted by name when `build()` is called.
452 pub fn add_subtree_parent(
453 mut self,
454 subtree_name: impl Into<String>,
455 parent_id: impl Into<String>,
456 ) -> Self {
457 let subtree_name = subtree_name.into();
458 let parent_id = parent_id.into();
459 if let Some(node) = self
460 .subtrees
461 .iter_mut()
462 .find(|node| node.name == subtree_name)
463 {
464 node.parents.push(parent_id.into());
465 } else {
466 self.subtrees.push(SubTreeNode {
467 name: subtree_name,
468 data: None,
469 parents: vec![parent_id.into()],
470 height: None,
471 });
472 }
473 self
474 }
475
476 /// Mutable reference version of add_subtree_parent.
477 /// Add a single parent ID to a specific named subtree's history.
478 /// If the subtree does not exist, it will be created with no data (`None`).
479 /// Parent IDs will be sorted and de-duplicated during the `build()` process.
480 /// The list of subtrees will be sorted by name when `build()` is called.
481 pub fn add_subtree_parent_mut(
482 &mut self,
483 subtree_name: impl Into<String>,
484 parent_id: impl Into<String>,
485 ) -> &mut Self {
486 let subtree_name = subtree_name.into();
487 let parent_id = parent_id.into();
488 if let Some(node) = self
489 .subtrees
490 .iter_mut()
491 .find(|node| node.name == subtree_name)
492 {
493 node.parents.push(parent_id.into());
494 } else {
495 self.subtrees.push(SubTreeNode {
496 name: subtree_name,
497 data: None,
498 parents: vec![parent_id.into()],
499 height: None,
500 });
501 }
502 self
503 }
504
505 /// Set the metadata for this entry's tree node.
506 ///
507 /// Metadata is optional information attached to an entry that is not part of the
508 /// main data model and is not merged between entries. It's used primarily for
509 /// improving efficiency of operations and for experimentation.
510 ///
511 /// For example, metadata can contain references to the current tips of the settings
512 /// subtree, allowing for efficient verification in sparse checkout scenarios.
513 ///
514 /// # Arguments
515 /// * `metadata` - `RawData` (serialized string) for the main tree node metadata.
516 ///
517 /// # Returns
518 /// Self for method chaining.
519 pub fn set_metadata(mut self, metadata: impl Into<String>) -> Self {
520 self.tree.metadata = Some(metadata.into());
521 self
522 }
523
524 /// Mutable reference version of set_metadata.
525 /// Set the metadata for this entry's tree node.
526 ///
527 /// Metadata is optional information attached to an entry that is not part of the
528 /// main data model and is not merged between entries. It's used primarily for
529 /// improving efficiency of operations and for experimentation.
530 ///
531 /// For example, metadata can contain references to the current tips of the settings
532 /// subtree, allowing for efficient verification in sparse checkout scenarios.
533 ///
534 /// # Arguments
535 /// * `metadata` - `RawData` (serialized string) for the main tree node metadata.
536 ///
537 /// # Returns
538 /// A mutable reference to self for method chaining.
539 pub fn set_metadata_mut(&mut self, metadata: impl Into<String>) -> &mut Self {
540 self.tree.metadata = Some(metadata.into());
541 self
542 }
543
544 /// Get the current metadata value for this entry builder.
545 ///
546 /// Metadata is optional information attached to an entry that is not part of the
547 /// main data model and is not merged between entries. It's used primarily for
548 /// improving efficiency of operations and for experimentation.
549 ///
550 /// # Returns
551 ///
552 /// Returns `Some(&RawData)` containing the serialized metadata if present,
553 /// or `None` if no metadata has been set.
554 ///
555 /// # Example
556 ///
557 /// ```ignore
558 /// let builder = Entry::builder("root_id");
559 /// assert!(builder.metadata().is_none());
560 ///
561 /// let builder = builder.set_metadata(r#"{"custom": "data"}"#);
562 /// assert!(builder.metadata().is_some());
563 /// ```
564 pub fn metadata(&self) -> Option<&RawData> {
565 self.tree.metadata.as_ref()
566 }
567
568 /// Set the height for this entry in the main tree DAG.
569 ///
570 /// # Arguments
571 /// * `height` - The height value for this entry
572 pub fn set_height(mut self, height: u64) -> Self {
573 self.tree.height = height;
574 self
575 }
576
577 /// Mutable reference version of set_height.
578 pub fn set_height_mut(&mut self, height: u64) -> &mut Self {
579 self.tree.height = height;
580 self
581 }
582
583 /// Set the height for this entry in a specific subtree's DAG.
584 ///
585 /// If the subtree does not exist, it will be created with no data (`None`).
586 ///
587 /// # Arguments
588 /// * `subtree_name` - The name of the subtree
589 /// * `height` - The height value for this entry in the subtree
590 pub fn set_subtree_height(
591 mut self,
592 subtree_name: impl Into<String>,
593 height: Option<u64>,
594 ) -> Self {
595 let subtree_name = subtree_name.into();
596 if let Some(node) = self
597 .subtrees
598 .iter_mut()
599 .find(|node| node.name == subtree_name)
600 {
601 node.height = height;
602 } else {
603 self.subtrees.push(SubTreeNode {
604 name: subtree_name,
605 data: None,
606 parents: vec![],
607 height,
608 });
609 }
610 self
611 }
612
613 /// Mutable reference version of set_subtree_height.
614 pub fn set_subtree_height_mut(
615 &mut self,
616 subtree_name: impl Into<String>,
617 height: Option<u64>,
618 ) -> &mut Self {
619 let subtree_name = subtree_name.into();
620 if let Some(node) = self
621 .subtrees
622 .iter_mut()
623 .find(|node| node.name == subtree_name)
624 {
625 node.height = height;
626 } else {
627 self.subtrees.push(SubTreeNode {
628 name: subtree_name,
629 data: None,
630 parents: vec![],
631 height,
632 });
633 }
634 self
635 }
636
637 /// Build and return the final immutable `Entry`.
638 ///
639 /// This method:
640 /// 1. Sorts all parent lists in both the main tree and subtrees
641 /// 2. Sorts the subtrees list by name
642 /// 3. Removes any empty subtrees
643 /// 4. Creates the immutable `Entry`
644 /// 5. Validates the entry structure
645 /// 6. Returns the validated entry or an error
646 ///
647 /// After calling this method, the builder is consumed and cannot be used again.
648 /// The returned `Entry` is immutable and its parts cannot be modified.
649 ///
650 /// # Returns
651 ///
652 /// - `Ok(Entry)` if the entry is structurally valid
653 /// - `Err(crate::Error)` if the entry fails validation (e.g., root entry with parents)
654 pub fn build(mut self) -> Result<Entry> {
655 // Sort parent lists (if any)
656 Self::sort_parents_list(&mut self.tree.parents);
657 for subtree in &mut self.subtrees {
658 Self::sort_parents_list(&mut subtree.parents);
659 }
660
661 // Deduplicate parents
662 self.tree.parents.dedup();
663 for subtree in &mut self.subtrees {
664 subtree.parents.dedup();
665 }
666
667 // Sort subtrees
668 self.sort_subtrees_list();
669
670 let entry = Entry {
671 version: ENTRY_VERSION,
672 tree: self.tree,
673 subtrees: self.subtrees,
674 sig: self.sig,
675 };
676
677 // Validate the built entry before returning
678 entry.validate()?;
679
680 Ok(entry)
681 }
682}