git_odb/
traits.rs

1use std::io;
2
3use git_object::WriteTo;
4
5/// Describe the capability to write git objects into an object store.
6pub trait Write {
7    /// The error type used for all trait methods.
8    ///
9    /// _Note_ the default implementations require the `From<io::Error>` bound.
10    type Error: std::error::Error + From<io::Error>;
11
12    /// Write objects using the intrinsic kind of [`hash`][git_hash::Kind] into the database,
13    /// returning id to reference it in subsequent reads.
14    fn write(&self, object: impl WriteTo) -> Result<git_hash::ObjectId, Self::Error> {
15        let mut buf = Vec::with_capacity(2048);
16        object.write_to(&mut buf)?;
17        self.write_stream(object.kind(), buf.len() as u64, buf.as_slice())
18    }
19    /// As [`write`][Write::write], but takes an [`object` kind][git_object::Kind] along with its encoded bytes.
20    fn write_buf(&self, object: git_object::Kind, from: &[u8]) -> Result<git_hash::ObjectId, Self::Error> {
21        self.write_stream(object, from.len() as u64, from)
22    }
23    /// As [`write`][Write::write], but takes an input stream.
24    /// This is commonly used for writing blobs directly without reading them to memory first.
25    fn write_stream(
26        &self,
27        kind: git_object::Kind,
28        size: u64,
29        from: impl io::Read,
30    ) -> Result<git_hash::ObjectId, Self::Error>;
31}
32
33/// Describe how object can be located in an object store.
34///
35/// ## Notes
36///
37/// Find effectively needs [generic associated types][issue] to allow a trait for the returned object type.
38/// Until then, we will have to make due with explicit types and give them the potentially added features we want.
39///
40/// [issue]: https://github.com/rust-lang/rust/issues/44265
41pub trait Find {
42    /// The error returned by [`try_find()`][Find::try_find()]
43    type Error: std::error::Error + 'static;
44
45    /// Returns true if the object exists in the database.
46    fn contains(&self, id: impl AsRef<git_hash::oid>) -> bool;
47
48    /// Find an object matching `id` in the database while placing its raw, possibly encoded data into `buffer`.
49    ///
50    /// Returns `Some` object if it was present in the database, or the error that occurred during lookup or object
51    /// retrieval.
52    fn try_find<'a>(
53        &self,
54        id: impl AsRef<git_hash::oid>,
55        buffer: &'a mut Vec<u8>,
56    ) -> Result<Option<git_object::Data<'a>>, Self::Error>;
57}
58
59/// A way to obtain object properties without fully decoding it.
60pub trait Header {
61    /// The error returned by [`try_header()`][Header::try_header()].
62    type Error: std::error::Error + 'static;
63    /// Try to read the header of the object associated with `id` or return `None` if it could not be found.
64    fn try_header(&self, id: impl AsRef<git_hash::oid>) -> Result<Option<find::Header>, Self::Error>;
65}
66
67mod _impls {
68    use std::{io::Read, ops::Deref, rc::Rc, sync::Arc};
69
70    use git_hash::{oid, ObjectId};
71    use git_object::{Data, Kind, WriteTo};
72
73    use crate::find::Header;
74
75    impl<T> crate::Write for &T
76    where
77        T: crate::Write,
78    {
79        type Error = T::Error;
80
81        fn write(&self, object: impl WriteTo) -> Result<ObjectId, Self::Error> {
82            (*self).write(object)
83        }
84
85        fn write_buf(&self, object: Kind, from: &[u8]) -> Result<ObjectId, Self::Error> {
86            (*self).write_buf(object, from)
87        }
88
89        fn write_stream(&self, kind: Kind, size: u64, from: impl Read) -> Result<ObjectId, Self::Error> {
90            (*self).write_stream(kind, size, from)
91        }
92    }
93
94    impl<T> crate::Write for Arc<T>
95    where
96        T: crate::Write,
97    {
98        type Error = T::Error;
99
100        fn write(&self, object: impl WriteTo) -> Result<ObjectId, Self::Error> {
101            self.deref().write(object)
102        }
103
104        fn write_buf(&self, object: Kind, from: &[u8]) -> Result<ObjectId, Self::Error> {
105            self.deref().write_buf(object, from)
106        }
107
108        fn write_stream(&self, kind: Kind, size: u64, from: impl Read) -> Result<ObjectId, Self::Error> {
109            self.deref().write_stream(kind, size, from)
110        }
111    }
112
113    impl<T> crate::Write for Rc<T>
114    where
115        T: crate::Write,
116    {
117        type Error = T::Error;
118
119        fn write(&self, object: impl WriteTo) -> Result<ObjectId, Self::Error> {
120            self.deref().write(object)
121        }
122
123        fn write_buf(&self, object: Kind, from: &[u8]) -> Result<ObjectId, Self::Error> {
124            self.deref().write_buf(object, from)
125        }
126
127        fn write_stream(&self, kind: Kind, size: u64, from: impl Read) -> Result<ObjectId, Self::Error> {
128            self.deref().write_stream(kind, size, from)
129        }
130    }
131
132    impl<T> crate::Find for &T
133    where
134        T: crate::Find,
135    {
136        type Error = T::Error;
137
138        fn contains(&self, id: impl AsRef<oid>) -> bool {
139            (*self).contains(id)
140        }
141
142        fn try_find<'a>(&self, id: impl AsRef<oid>, buffer: &'a mut Vec<u8>) -> Result<Option<Data<'a>>, Self::Error> {
143            (*self).try_find(id, buffer)
144        }
145    }
146
147    impl<T> crate::Header for &T
148    where
149        T: crate::Header,
150    {
151        type Error = T::Error;
152
153        fn try_header(&self, id: impl AsRef<oid>) -> Result<Option<Header>, Self::Error> {
154            (*self).try_header(id)
155        }
156    }
157
158    impl<T> crate::Find for Rc<T>
159    where
160        T: crate::Find,
161    {
162        type Error = T::Error;
163
164        fn contains(&self, id: impl AsRef<oid>) -> bool {
165            self.deref().contains(id)
166        }
167
168        fn try_find<'a>(&self, id: impl AsRef<oid>, buffer: &'a mut Vec<u8>) -> Result<Option<Data<'a>>, Self::Error> {
169            self.deref().try_find(id, buffer)
170        }
171    }
172
173    impl<T> crate::Header for Rc<T>
174    where
175        T: crate::Header,
176    {
177        type Error = T::Error;
178
179        fn try_header(&self, id: impl AsRef<oid>) -> Result<Option<Header>, Self::Error> {
180            self.deref().try_header(id)
181        }
182    }
183
184    impl<T> crate::Find for Arc<T>
185    where
186        T: crate::Find,
187    {
188        type Error = T::Error;
189
190        fn contains(&self, id: impl AsRef<oid>) -> bool {
191            self.deref().contains(id)
192        }
193
194        fn try_find<'a>(&self, id: impl AsRef<oid>, buffer: &'a mut Vec<u8>) -> Result<Option<Data<'a>>, Self::Error> {
195            self.deref().try_find(id, buffer)
196        }
197    }
198
199    impl<T> crate::Header for Arc<T>
200    where
201        T: crate::Header,
202    {
203        type Error = T::Error;
204
205        fn try_header(&self, id: impl AsRef<oid>) -> Result<Option<Header>, Self::Error> {
206            self.deref().try_header(id)
207        }
208    }
209}
210
211mod ext {
212    use git_object::{BlobRef, CommitRef, CommitRefIter, Kind, ObjectRef, TagRef, TagRefIter, TreeRef, TreeRefIter};
213
214    use crate::find;
215
216    macro_rules! make_obj_lookup {
217        ($method:ident, $object_variant:path, $object_kind:path, $object_type:ty) => {
218            /// Like [`find(…)`][Self::find()], but flattens the `Result<Option<_>>` into a single `Result` making a non-existing object an error
219            /// while returning the desired object type.
220            fn $method<'a>(
221                &self,
222                id: impl AsRef<git_hash::oid>,
223                buffer: &'a mut Vec<u8>,
224            ) -> Result<$object_type, find::existing_object::Error<Self::Error>> {
225                let id = id.as_ref();
226                self.try_find(id, buffer)
227                    .map_err(find::existing_object::Error::Find)?
228                    .ok_or_else(|| find::existing_object::Error::NotFound {
229                        oid: id.as_ref().to_owned(),
230                    })
231                    .and_then(|o| o.decode().map_err(find::existing_object::Error::Decode))
232                    .and_then(|o| match o {
233                        $object_variant(o) => return Ok(o),
234                        _other => Err(find::existing_object::Error::ObjectKind {
235                            expected: $object_kind,
236                        }),
237                    })
238            }
239        };
240    }
241
242    macro_rules! make_iter_lookup {
243        ($method:ident, $object_kind:path, $object_type:ty, $into_iter:tt) => {
244            /// Like [`find(…)`][Self::find()], but flattens the `Result<Option<_>>` into a single `Result` making a non-existing object an error
245            /// while returning the desired iterator type.
246            fn $method<'a>(
247                &self,
248                id: impl AsRef<git_hash::oid>,
249                buffer: &'a mut Vec<u8>,
250            ) -> Result<$object_type, find::existing_iter::Error<Self::Error>> {
251                let id = id.as_ref();
252                self.try_find(id, buffer)
253                    .map_err(find::existing_iter::Error::Find)?
254                    .ok_or_else(|| find::existing_iter::Error::NotFound {
255                        oid: id.as_ref().to_owned(),
256                    })
257                    .and_then(|o| {
258                        o.$into_iter()
259                            .ok_or_else(|| find::existing_iter::Error::ObjectKind {
260                                expected: $object_kind,
261                            })
262                    })
263            }
264        };
265    }
266
267    /// An extension trait with convenience functions.
268    pub trait HeaderExt: super::Header {
269        /// Like [`try_header(…)`][super::Header::try_header()], but flattens the `Result<Option<_>>` into a single `Result` making a non-existing object an error.
270        fn header(
271            &self,
272            id: impl AsRef<git_hash::oid>,
273        ) -> Result<crate::find::Header, find::existing::Error<Self::Error>> {
274            let id = id.as_ref();
275            self.try_header(id)
276                .map_err(find::existing::Error::Find)?
277                .ok_or_else(|| find::existing::Error::NotFound { oid: id.to_owned() })
278        }
279    }
280
281    impl<T: super::Header> HeaderExt for T {}
282
283    /// An extension trait with convenience functions.
284    pub trait FindExt: super::Find {
285        /// Like [`try_find(…)`][super::Find::try_find()], but flattens the `Result<Option<_>>` into a single `Result` making a non-existing object an error.
286        fn find<'a>(
287            &self,
288            id: impl AsRef<git_hash::oid>,
289            buffer: &'a mut Vec<u8>,
290        ) -> Result<git_object::Data<'a>, find::existing::Error<Self::Error>> {
291            let id = id.as_ref();
292            self.try_find(id, buffer)
293                .map_err(find::existing::Error::Find)?
294                .ok_or_else(|| find::existing::Error::NotFound { oid: id.to_owned() })
295        }
296
297        make_obj_lookup!(find_commit, ObjectRef::Commit, Kind::Commit, CommitRef<'a>);
298        make_obj_lookup!(find_tree, ObjectRef::Tree, Kind::Tree, TreeRef<'a>);
299        make_obj_lookup!(find_tag, ObjectRef::Tag, Kind::Tag, TagRef<'a>);
300        make_obj_lookup!(find_blob, ObjectRef::Blob, Kind::Blob, BlobRef<'a>);
301        make_iter_lookup!(find_commit_iter, Kind::Commit, CommitRefIter<'a>, try_into_commit_iter);
302        make_iter_lookup!(find_tree_iter, Kind::Tree, TreeRefIter<'a>, try_into_tree_iter);
303        make_iter_lookup!(find_tag_iter, Kind::Tag, TagRefIter<'a>, try_into_tag_iter);
304    }
305
306    impl<T: super::Find> FindExt for T {}
307}
308pub use ext::{FindExt, HeaderExt};
309
310use crate::find;