use std::io; use gix_object::WriteTo; /// Describe the capability to write git objects into an object store. pub trait Write { /// The error type used for all trait methods. /// /// _Note_ the default implementations require the `From` bound. type Error: std::error::Error + From; /// Write objects using the intrinsic kind of [`hash`][gix_hash::Kind] into the database, /// returning id to reference it in subsequent reads. fn write(&self, object: impl WriteTo) -> Result { let mut buf = Vec::with_capacity(2048); object.write_to(&mut buf)?; self.write_stream(object.kind(), buf.len() as u64, buf.as_slice()) } /// As [`write`][Write::write], but takes an [`object` kind][gix_object::Kind] along with its encoded bytes. fn write_buf(&self, object: gix_object::Kind, from: &[u8]) -> Result { self.write_stream(object, from.len() as u64, from) } /// As [`write`][Write::write], but takes an input stream. /// This is commonly used for writing blobs directly without reading them to memory first. fn write_stream( &self, kind: gix_object::Kind, size: u64, from: impl io::Read, ) -> Result; } /// Describe how object can be located in an object store. /// /// ## Notes /// /// Find effectively needs [generic associated types][issue] to allow a trait for the returned object type. /// Until then, we will have to make due with explicit types and give them the potentially added features we want. /// /// [issue]: https://github.com/rust-lang/rust/issues/44265 pub trait Find { /// The error returned by [`try_find()`][Find::try_find()] type Error: std::error::Error + 'static; /// Returns true if the object exists in the database. fn contains(&self, id: impl AsRef) -> bool; /// Find an object matching `id` in the database while placing its raw, possibly encoded data into `buffer`. /// /// Returns `Some` object if it was present in the database, or the error that occurred during lookup or object /// retrieval. fn try_find<'a>( &self, id: impl AsRef, buffer: &'a mut Vec, ) -> Result>, Self::Error>; } /// A way to obtain object properties without fully decoding it. pub trait Header { /// The error returned by [`try_header()`][Header::try_header()]. type Error: std::error::Error + 'static; /// Try to read the header of the object associated with `id` or return `None` if it could not be found. fn try_header(&self, id: impl AsRef) -> Result, Self::Error>; } mod _impls { use std::{io::Read, ops::Deref, rc::Rc, sync::Arc}; use gix_hash::{oid, ObjectId}; use gix_object::{Data, Kind, WriteTo}; use crate::find::Header; impl crate::Write for &T where T: crate::Write, { type Error = T::Error; fn write(&self, object: impl WriteTo) -> Result { (*self).write(object) } fn write_buf(&self, object: Kind, from: &[u8]) -> Result { (*self).write_buf(object, from) } fn write_stream(&self, kind: Kind, size: u64, from: impl Read) -> Result { (*self).write_stream(kind, size, from) } } impl crate::Write for Arc where T: crate::Write, { type Error = T::Error; fn write(&self, object: impl WriteTo) -> Result { self.deref().write(object) } fn write_buf(&self, object: Kind, from: &[u8]) -> Result { self.deref().write_buf(object, from) } fn write_stream(&self, kind: Kind, size: u64, from: impl Read) -> Result { self.deref().write_stream(kind, size, from) } } impl crate::Write for Rc where T: crate::Write, { type Error = T::Error; fn write(&self, object: impl WriteTo) -> Result { self.deref().write(object) } fn write_buf(&self, object: Kind, from: &[u8]) -> Result { self.deref().write_buf(object, from) } fn write_stream(&self, kind: Kind, size: u64, from: impl Read) -> Result { self.deref().write_stream(kind, size, from) } } impl crate::Find for &T where T: crate::Find, { type Error = T::Error; fn contains(&self, id: impl AsRef) -> bool { (*self).contains(id) } fn try_find<'a>(&self, id: impl AsRef, buffer: &'a mut Vec) -> Result>, Self::Error> { (*self).try_find(id, buffer) } } impl crate::Header for &T where T: crate::Header, { type Error = T::Error; fn try_header(&self, id: impl AsRef) -> Result, Self::Error> { (*self).try_header(id) } } impl crate::Find for Rc where T: crate::Find, { type Error = T::Error; fn contains(&self, id: impl AsRef) -> bool { self.deref().contains(id) } fn try_find<'a>(&self, id: impl AsRef, buffer: &'a mut Vec) -> Result>, Self::Error> { self.deref().try_find(id, buffer) } } impl crate::Header for Rc where T: crate::Header, { type Error = T::Error; fn try_header(&self, id: impl AsRef) -> Result, Self::Error> { self.deref().try_header(id) } } impl crate::Find for Arc where T: crate::Find, { type Error = T::Error; fn contains(&self, id: impl AsRef) -> bool { self.deref().contains(id) } fn try_find<'a>(&self, id: impl AsRef, buffer: &'a mut Vec) -> Result>, Self::Error> { self.deref().try_find(id, buffer) } } impl crate::Header for Arc where T: crate::Header, { type Error = T::Error; fn try_header(&self, id: impl AsRef) -> Result, Self::Error> { self.deref().try_header(id) } } } mod ext { use gix_object::{BlobRef, CommitRef, CommitRefIter, Kind, ObjectRef, TagRef, TagRefIter, TreeRef, TreeRefIter}; use crate::find; macro_rules! make_obj_lookup { ($method:ident, $object_variant:path, $object_kind:path, $object_type:ty) => { /// Like [`find(…)`][Self::find()], but flattens the `Result>` into a single `Result` making a non-existing object an error /// while returning the desired object type. fn $method<'a>( &self, id: impl AsRef, buffer: &'a mut Vec, ) -> Result<$object_type, find::existing_object::Error> { let id = id.as_ref(); self.try_find(id, buffer) .map_err(find::existing_object::Error::Find)? .ok_or_else(|| find::existing_object::Error::NotFound { oid: id.as_ref().to_owned(), }) .and_then(|o| o.decode().map_err(find::existing_object::Error::Decode)) .and_then(|o| match o { $object_variant(o) => return Ok(o), _other => Err(find::existing_object::Error::ObjectKind { expected: $object_kind, }), }) } }; } macro_rules! make_iter_lookup { ($method:ident, $object_kind:path, $object_type:ty, $into_iter:tt) => { /// Like [`find(…)`][Self::find()], but flattens the `Result>` into a single `Result` making a non-existing object an error /// while returning the desired iterator type. fn $method<'a>( &self, id: impl AsRef, buffer: &'a mut Vec, ) -> Result<$object_type, find::existing_iter::Error> { let id = id.as_ref(); self.try_find(id, buffer) .map_err(find::existing_iter::Error::Find)? .ok_or_else(|| find::existing_iter::Error::NotFound { oid: id.as_ref().to_owned(), }) .and_then(|o| { o.$into_iter() .ok_or_else(|| find::existing_iter::Error::ObjectKind { expected: $object_kind, }) }) } }; } /// An extension trait with convenience functions. pub trait HeaderExt: super::Header { /// Like [`try_header(…)`][super::Header::try_header()], but flattens the `Result>` into a single `Result` making a non-existing object an error. fn header( &self, id: impl AsRef, ) -> Result> { let id = id.as_ref(); self.try_header(id) .map_err(find::existing::Error::Find)? .ok_or_else(|| find::existing::Error::NotFound { oid: id.to_owned() }) } } impl HeaderExt for T {} /// An extension trait with convenience functions. pub trait FindExt: super::Find { /// Like [`try_find(…)`][super::Find::try_find()], but flattens the `Result>` into a single `Result` making a non-existing object an error. fn find<'a>( &self, id: impl AsRef, buffer: &'a mut Vec, ) -> Result, find::existing::Error> { let id = id.as_ref(); self.try_find(id, buffer) .map_err(find::existing::Error::Find)? .ok_or_else(|| find::existing::Error::NotFound { oid: id.to_owned() }) } make_obj_lookup!(find_commit, ObjectRef::Commit, Kind::Commit, CommitRef<'a>); make_obj_lookup!(find_tree, ObjectRef::Tree, Kind::Tree, TreeRef<'a>); make_obj_lookup!(find_tag, ObjectRef::Tag, Kind::Tag, TagRef<'a>); make_obj_lookup!(find_blob, ObjectRef::Blob, Kind::Blob, BlobRef<'a>); make_iter_lookup!(find_commit_iter, Kind::Commit, CommitRefIter<'a>, try_into_commit_iter); make_iter_lookup!(find_tree_iter, Kind::Tree, TreeRefIter<'a>, try_into_tree_iter); make_iter_lookup!(find_tag_iter, Kind::Tag, TagRefIter<'a>, try_into_tag_iter); } impl FindExt for T {} } pub use ext::{FindExt, HeaderExt}; use crate::find;