Skip to main content

heddle_object_model/object/
source.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Read-only object source traits for graph walkers.
3
4use super::{Blob, ContentHash, State, StateId, Tree};
5use crate::error::{HeddleError, Result};
6
7/// Read-only object access needed by object graph walkers.
8pub trait ObjectSource {
9    fn get_tree(&self, hash: &ContentHash) -> Result<Option<Tree>>;
10    fn get_state(&self, id: &StateId) -> Result<Option<State>>;
11    fn get_blob(&self, hash: &ContentHash) -> Result<Option<Blob>>;
12
13    /// Resolve a recorded tree hash; absence is never an empty tree.
14    fn require_tree(&self, hash: &ContentHash) -> Result<Tree> {
15        self.get_tree(hash)?
16            .ok_or_else(|| HeddleError::MissingObject {
17                object_type: "tree".to_string(),
18                id: hash.to_hex(),
19            })
20    }
21
22    /// Resolve a recorded blob hash; absence is never empty content.
23    fn require_blob(&self, hash: &ContentHash) -> Result<Blob> {
24        self.get_blob(hash)?
25            .ok_or_else(|| HeddleError::MissingObject {
26                object_type: "blob".to_string(),
27                id: hash.to_hex(),
28            })
29    }
30
31    /// Uncompressed byte length without requiring content.
32    ///
33    /// The default falls back to [`Self::get_blob`]. Stores that can answer
34    /// from a header or index should override this so blame can reject an
35    /// oversized blob before materializing it.
36    fn decoded_blob_len(&self, hash: &ContentHash) -> Result<Option<u64>> {
37        Ok(self.get_blob(hash)?.map(|blob| blob.content().len() as u64))
38    }
39
40    /// Zero-copy variant of `get_blob`.
41    fn get_blob_bytes(&self, hash: &ContentHash) -> Result<Option<bytes::Bytes>> {
42        Ok(self
43            .get_blob(hash)?
44            .map(|blob| bytes::Bytes::from(blob.into_content())))
45    }
46}
47
48#[cfg(feature = "async-source")]
49#[allow(async_fn_in_trait)]
50pub trait AsyncObjectSource {
51    async fn require_tree(&self, hash: &ContentHash) -> Result<Tree> {
52        self.get_tree(hash)
53            .await?
54            .ok_or_else(|| HeddleError::MissingObject {
55                object_type: "tree".to_string(),
56                id: hash.to_hex(),
57            })
58    }
59
60    async fn require_blob(&self, hash: &ContentHash) -> Result<Blob> {
61        self.get_blob(hash)
62            .await?
63            .ok_or_else(|| HeddleError::MissingObject {
64                object_type: "blob".to_string(),
65                id: hash.to_hex(),
66            })
67    }
68
69    async fn get_tree(&self, hash: &ContentHash) -> Result<Option<Tree>>;
70    async fn get_state(&self, id: &StateId) -> Result<Option<State>>;
71    async fn get_blob(&self, hash: &ContentHash) -> Result<Option<Blob>>;
72}