Development Documentation (main branch) - For stable release docs, see docs.rs/eidetica

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}