Skip to main content

gix_object/traits/
find.rs

1use gix_error::ExnResult;
2/// Check if an object is present in an object store.
3pub trait Exists {
4    /// Returns `true` if the object exists in the database.
5    fn exists(&self, id: &gix_hash::oid) -> bool;
6}
7
8/// Find an object in the object store.
9///
10/// ## Notes
11///
12/// Find effectively needs [generic associated types][issue] to allow a trait for the returned object type.
13/// Until then, we will have to make due with explicit types and give them the potentially added features we want.
14///
15/// [issue]: https://github.com/rust-lang/rust/issues/44265
16pub trait Find {
17    /// Find an object matching `id` in the database while placing its raw, possibly encoded data into `buffer`.
18    ///
19    /// Returns `Some` object if it was present in the database, or the error that occurred during lookup or object
20    /// retrieval.
21    fn try_find<'a>(&self, id: &gix_hash::oid, buffer: &'a mut Vec<u8>) -> ExnResult<Option<crate::Data<'a>>>;
22}
23
24/// Find the header of an object in the object store.
25pub trait Header {
26    /// Find the header of the object matching `id` in the database.
27    ///
28    /// Returns `Some` header if it was present, or the error that occurred during lookup.
29    fn try_header(&self, id: &gix_hash::oid) -> ExnResult<Option<crate::Header>>;
30}
31
32/// A combination of [`Find`] and [`Header`] traits to help with `dyn` trait objects.
33pub trait FindObjectOrHeader: Find + Header {}
34
35mod _impls {
36    use std::{ops::Deref, rc::Rc, sync::Arc};
37
38    use gix_error::ExnResult;
39
40    use gix_hash::oid;
41
42    use crate::Data;
43
44    impl<T> crate::Exists for &T
45    where
46        T: crate::Exists,
47    {
48        fn exists(&self, id: &oid) -> bool {
49            (*self).exists(id)
50        }
51    }
52
53    impl<T> crate::FindObjectOrHeader for T where T: crate::Find + crate::FindHeader {}
54
55    impl<T> crate::Find for &T
56    where
57        T: crate::Find,
58    {
59        fn try_find<'a>(&self, id: &oid, buffer: &'a mut Vec<u8>) -> ExnResult<Option<Data<'a>>> {
60            (*self).try_find(id, buffer)
61        }
62    }
63
64    impl<T> crate::FindHeader for &T
65    where
66        T: crate::FindHeader,
67    {
68        fn try_header(&self, id: &gix_hash::oid) -> ExnResult<Option<crate::Header>> {
69            (*self).try_header(id)
70        }
71    }
72
73    impl<T> crate::Exists for Box<T>
74    where
75        T: crate::Exists,
76    {
77        fn exists(&self, id: &oid) -> bool {
78            self.deref().exists(id)
79        }
80    }
81
82    impl<T> crate::Exists for Rc<T>
83    where
84        T: crate::Exists,
85    {
86        fn exists(&self, id: &oid) -> bool {
87            self.deref().exists(id)
88        }
89    }
90
91    impl<T> crate::Find for Rc<T>
92    where
93        T: crate::Find,
94    {
95        fn try_find<'a>(&self, id: &oid, buffer: &'a mut Vec<u8>) -> ExnResult<Option<Data<'a>>> {
96            self.deref().try_find(id, buffer)
97        }
98    }
99
100    impl<T> crate::FindHeader for Rc<T>
101    where
102        T: crate::FindHeader,
103    {
104        fn try_header(&self, id: &gix_hash::oid) -> ExnResult<Option<crate::Header>> {
105            self.deref().try_header(id)
106        }
107    }
108
109    impl<T> crate::Find for Box<T>
110    where
111        T: crate::Find,
112    {
113        fn try_find<'a>(&self, id: &oid, buffer: &'a mut Vec<u8>) -> ExnResult<Option<Data<'a>>> {
114            self.deref().try_find(id, buffer)
115        }
116    }
117
118    impl<T> crate::FindHeader for Box<T>
119    where
120        T: crate::FindHeader,
121    {
122        fn try_header(&self, id: &gix_hash::oid) -> ExnResult<Option<crate::Header>> {
123            self.deref().try_header(id)
124        }
125    }
126
127    impl<T> crate::Exists for Arc<T>
128    where
129        T: crate::Exists,
130    {
131        fn exists(&self, id: &oid) -> bool {
132            self.deref().exists(id)
133        }
134    }
135
136    impl<T> crate::Find for Arc<T>
137    where
138        T: crate::Find,
139    {
140        fn try_find<'a>(&self, id: &oid, buffer: &'a mut Vec<u8>) -> ExnResult<Option<Data<'a>>> {
141            self.deref().try_find(id, buffer)
142        }
143    }
144
145    impl<T> crate::FindHeader for Arc<T>
146    where
147        T: crate::FindHeader,
148    {
149        fn try_header(&self, id: &gix_hash::oid) -> ExnResult<Option<crate::Header>> {
150            self.deref().try_header(id)
151        }
152    }
153}
154
155mod ext {
156    use gix_error::{ErrorExt, ExnResult, ResultExt, corruption, validation};
157
158    use crate::{BlobRef, CommitRef, CommitRefIter, Kind, ObjectRef, TagRef, TagRefIter, TreeRef, TreeRefIter};
159
160    fn not_found(id: &gix_hash::oid) -> gix_error::Exn {
161        gix_error::not_found(format!("An object with id {id} could not be found")).raise_erased()
162    }
163
164    fn wrong_kind(id: &gix_hash::oid, actual: Kind, expected: Kind) -> gix_error::Exn {
165        validation(format!("Expected object of kind {expected} but got {actual} at {id}")).raise_erased()
166    }
167
168    macro_rules! make_obj_lookup {
169        ($method:ident, $object_variant:path, $object_kind:path, $object_type:ty) => {
170            /// Like [`find(…)`][Self::find()], but flattens the `Result<Option<_>>` into a single `Result` making a non-existing object an error
171            /// while returning the desired object type.
172            fn $method<'a>(&self, id: &gix_hash::oid, buffer: &'a mut Vec<u8>) -> ExnResult<$object_type> {
173                self.try_find(id, buffer)?
174                    .ok_or_else(|| not_found(id))
175                    .and_then(|o| {
176                        o.decode()
177                            .or_raise_erased(|| corruption(format!("Could not decode object at {id}")))
178                    })
179                    .and_then(|o| match o {
180                        $object_variant(o) => return Ok(o),
181                        o => Err(wrong_kind(id, o.kind(), $object_kind)),
182                    })
183            }
184        };
185    }
186
187    macro_rules! make_iter_lookup {
188        ($method:ident, $object_kind:path, $object_type:ty, $into_iter:tt) => {
189            /// Like [`find(…)`][Self::find()], but flattens the `Result<Option<_>>` into a single `Result` making a non-existing object an error
190            /// while returning the desired iterator type.
191            fn $method<'a>(&self, id: &gix_hash::oid, buffer: &'a mut Vec<u8>) -> ExnResult<$object_type> {
192                self.try_find(id, buffer)?
193                    .ok_or_else(|| not_found(id))
194                    .and_then(|o| o.$into_iter().ok_or_else(|| wrong_kind(id, o.kind, $object_kind)))
195            }
196        };
197    }
198
199    /// An extension trait with convenience functions.
200    pub trait HeaderExt: super::Header {
201        /// Like [`try_header(…)`](super::Header::try_header()), but flattens the `Result<Option<_>>` into a single `Result` making a non-existing header an error.
202        fn header(&self, id: &gix_hash::oid) -> ExnResult<crate::Header> {
203            self.try_header(id)?.ok_or_else(|| not_found(id))
204        }
205    }
206
207    /// An extension trait with convenience functions.
208    pub trait FindExt: super::Find {
209        /// Like [`try_find(…)`](super::Find::try_find()), but flattens the `Result<Option<_>>` into a single `Result` making a non-existing object an error.
210        fn find<'a>(&self, id: &gix_hash::oid, buffer: &'a mut Vec<u8>) -> ExnResult<crate::Data<'a>> {
211            self.try_find(id, buffer)?.ok_or_else(|| not_found(id))
212        }
213
214        /// Like [`find(…)`][Self::find()], but flattens the `Result<Option<_>>` into a single `Result` making a non-existing object an error
215        /// while returning the desired object type.
216        fn find_blob<'a>(&self, id: &gix_hash::oid, buffer: &'a mut Vec<u8>) -> ExnResult<BlobRef<'a>> {
217            if id == gix_hash::ObjectId::empty_blob(id.kind()) {
218                return Ok(BlobRef { data: &[] });
219            }
220            self.try_find(id, buffer)?
221                .ok_or_else(|| not_found(id))
222                .and_then(|o| {
223                    o.decode()
224                        .or_raise_erased(|| corruption(format!("Could not decode object at {id}")))
225                })
226                .and_then(|o| match o {
227                    ObjectRef::Blob(o) => Ok(o),
228                    o => Err(wrong_kind(id, o.kind(), Kind::Blob)),
229                })
230        }
231
232        /// Like [`find(…)`][Self::find()], but flattens the `Result<Option<_>>` into a single `Result` making a non-existing object an error
233        /// while returning the desired object type.
234        fn find_tree<'a>(&self, id: &gix_hash::oid, buffer: &'a mut Vec<u8>) -> ExnResult<TreeRef<'a>> {
235            if id == gix_hash::ObjectId::empty_tree(id.kind()) {
236                return Ok(TreeRef { entries: Vec::new() });
237            }
238            self.try_find(id, buffer)?
239                .ok_or_else(|| not_found(id))
240                .and_then(|o| {
241                    o.decode()
242                        .or_raise_erased(|| corruption(format!("Could not decode object at {id}")))
243                })
244                .and_then(|o| match o {
245                    ObjectRef::Tree(o) => Ok(o),
246                    o => Err(wrong_kind(id, o.kind(), Kind::Tree)),
247                })
248        }
249
250        make_obj_lookup!(find_commit, ObjectRef::Commit, Kind::Commit, CommitRef<'a>);
251        make_obj_lookup!(find_tag, ObjectRef::Tag, Kind::Tag, TagRef<'a>);
252        make_iter_lookup!(find_commit_iter, Kind::Commit, CommitRefIter<'a>, try_into_commit_iter);
253        make_iter_lookup!(find_tree_iter, Kind::Tree, TreeRefIter<'a>, try_into_tree_iter);
254        make_iter_lookup!(find_tag_iter, Kind::Tag, TagRefIter<'a>, try_into_tag_iter);
255    }
256
257    impl<T: super::Find + ?Sized> FindExt for T {}
258}
259pub use ext::{FindExt, HeaderExt};