Skip to main content

objects/object/
tree_struct.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Tree structure.
3
4use std::path::Path;
5
6use serde::{Deserialize, Serialize};
7
8use super::{ContentHash, TreeEntry, TreeError};
9
10/// A tree represents a directory structure.
11#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
12pub struct Tree {
13    entries: Vec<TreeEntry>,
14}
15
16impl Tree {
17    pub fn new() -> Self {
18        Self {
19            entries: Vec::new(),
20        }
21    }
22
23    pub fn from_entries(mut entries: Vec<TreeEntry>) -> Self {
24        entries.sort_by(|a, b| a.name.cmp(&b.name));
25        Self { entries }
26    }
27
28    pub fn validate(&self) -> Result<(), TreeError> {
29        let mut previous_name: Option<&str> = None;
30
31        for entry in &self.entries {
32            entry.validate()?;
33
34            if let Some(previous) = previous_name
35                && previous >= entry.name.as_str()
36            {
37                return Err(TreeError::InvalidStructure(
38                    "entries must be strictly sorted by name".to_string(),
39                ));
40            }
41
42            previous_name = Some(&entry.name);
43        }
44
45        Ok(())
46    }
47
48    pub fn entries(&self) -> &[TreeEntry] {
49        &self.entries
50    }
51
52    pub fn get(&self, name: &str) -> Option<&TreeEntry> {
53        let index = self
54            .entries
55            .binary_search_by(|entry| entry.name.as_str().cmp(name))
56            .ok()?;
57        self.entries.get(index)
58    }
59
60    pub fn insert(&mut self, entry: TreeEntry) {
61        self.entries.retain(|e| e.name != entry.name);
62
63        let pos = self
64            .entries
65            .iter()
66            .position(|e| e.name > entry.name)
67            .unwrap_or(self.entries.len());
68        self.entries.insert(pos, entry);
69    }
70
71    pub fn remove(&mut self, name: &str) -> Option<TreeEntry> {
72        let pos = self.entries.iter().position(|e| e.name == name)?;
73        Some(self.entries.remove(pos))
74    }
75
76    pub fn is_empty(&self) -> bool {
77        self.entries.is_empty()
78    }
79
80    pub fn len(&self) -> usize {
81        self.entries.len()
82    }
83
84    pub fn hash(&self) -> ContentHash {
85        let total_len: usize = self.entries.iter().map(TreeEntry::encoded_len).sum();
86        ContentHash::compute_typed_with_len("tree", total_len as u64, |hasher| {
87            for entry in &self.entries {
88                entry.update_hasher(hasher);
89            }
90        })
91    }
92
93    pub fn iter(&self) -> impl Iterator<Item = &TreeEntry> {
94        self.entries.iter()
95    }
96
97    pub fn get_path(&self, path: &Path) -> Option<&TreeEntry> {
98        let name = path.file_name()?.to_str()?;
99        if path.parent().is_none_or(|p| p.as_os_str().is_empty()) {
100            self.get(name)
101        } else {
102            None
103        }
104    }
105}
106
107impl Default for Tree {
108    fn default() -> Self {
109        Self::new()
110    }
111}
112
113impl IntoIterator for Tree {
114    type Item = TreeEntry;
115    type IntoIter = std::vec::IntoIter<TreeEntry>;
116
117    fn into_iter(self) -> Self::IntoIter {
118        self.entries.into_iter()
119    }
120}
121
122impl<'a> IntoIterator for &'a Tree {
123    type Item = &'a TreeEntry;
124    type IntoIter = std::slice::Iter<'a, TreeEntry>;
125
126    fn into_iter(self) -> Self::IntoIter {
127        self.entries.iter()
128    }
129}