git_chunk/file/
index.rs

1use std::ops::Range;
2
3use crate::file::Index;
4
5///
6pub mod offset_by_kind {
7    use std::fmt::{Display, Formatter};
8
9    /// The error returned by [Index::offset_by_kind()][super::Index::offset_by_id()].
10    #[allow(missing_docs)]
11    #[derive(Debug)]
12    pub struct Error {
13        pub kind: crate::Id,
14    }
15
16    impl Display for Error {
17        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
18            write!(
19                f,
20                "Chunk named {:?} was not found in chunk file index",
21                std::str::from_utf8(&self.kind).unwrap_or("<non-ascii>")
22            )
23        }
24    }
25
26    impl std::error::Error for Error {}
27}
28
29///
30pub mod data_by_kind {
31    /// The error returned by [Index::data_by_kind()][super::Index::data_by_id()].
32    #[derive(Debug, thiserror::Error)]
33    #[allow(missing_docs)]
34    pub enum Error {
35        #[error("The chunk wasn't found in the file index")]
36        NotFound(#[from] super::offset_by_kind::Error),
37        #[error("The offsets into the file couldn't be represented by usize")]
38        FileTooLarge,
39    }
40}
41
42/// An entry of a chunk file index
43pub struct Entry {
44    /// The kind of the chunk file
45    pub kind: crate::Id,
46    /// The offset, relative to the beginning of the file, at which to find the chunk and its end.
47    pub offset: Range<crate::file::Offset>,
48}
49
50impl Index {
51    /// The size of a single index entry in bytes
52    pub const ENTRY_SIZE: usize = std::mem::size_of::<u32>() + std::mem::size_of::<u64>();
53    /// The smallest possible size of an index, consisting only of the sentinel value pointing past itself.
54    pub const EMPTY_SIZE: usize = Index::ENTRY_SIZE;
55
56    /// Returns the size in bytes an index with `num_entries` would take.
57    pub const fn size_for_entries(num_entries: usize) -> usize {
58        Self::ENTRY_SIZE * (num_entries + 1/*sentinel*/)
59    }
60
61    /// Find a chunk of `kind` and return its offset into the data if found
62    pub fn offset_by_id(&self, kind: crate::Id) -> Result<Range<crate::file::Offset>, offset_by_kind::Error> {
63        self.chunks
64            .iter()
65            .find_map(|c| (c.kind == kind).then(|| c.offset.clone()))
66            .ok_or(offset_by_kind::Error { kind })
67    }
68
69    /// Find a chunk of `kind` and return its offset as usize range into the data if found.
70    ///
71    ///
72    /// # Panics
73    ///
74    /// - if the usize conversion fails, which isn't expected as memory maps can't be created if files are too large
75    ///   to require such offsets.
76    pub fn usize_offset_by_id(&self, kind: crate::Id) -> Result<Range<usize>, offset_by_kind::Error> {
77        self.chunks
78            .iter()
79            .find_map(|c| (c.kind == kind).then(|| crate::range::into_usize_or_panic(c.offset.clone())))
80            .ok_or(offset_by_kind::Error { kind })
81    }
82
83    /// Like [`Index::usize_offset_by_id()`] but with support for validation and transformation using a function.
84    pub fn validated_usize_offset_by_id<T>(
85        &self,
86        kind: crate::Id,
87        validate: impl FnOnce(Range<usize>) -> T,
88    ) -> Result<T, offset_by_kind::Error> {
89        self.chunks
90            .iter()
91            .find_map(|c| (c.kind == kind).then(|| crate::range::into_usize_or_panic(c.offset.clone())))
92            .map(validate)
93            .ok_or(offset_by_kind::Error { kind })
94    }
95
96    /// Find a chunk of `kind` and return its data slice based on its offset.
97    pub fn data_by_id<'a>(&self, data: &'a [u8], kind: crate::Id) -> Result<&'a [u8], data_by_kind::Error> {
98        let offset = self.offset_by_id(kind)?;
99        Ok(&data[crate::range::into_usize(offset).ok_or(data_by_kind::Error::FileTooLarge)?])
100    }
101
102    /// Return the end offset lf the last chunk, which is the highest offset as well.
103    /// It's definitely available as we have one or more chunks.
104    pub fn highest_offset(&self) -> crate::file::Offset {
105        self.chunks.last().expect("at least one chunk").offset.end
106    }
107}