make origin store errors be actual errors, and implement a mock

This commit is contained in:
Brian Picciano 2023-05-09 13:40:49 +02:00
parent 696c92a292
commit be84742e94
3 changed files with 109 additions and 74 deletions

1
Cargo.lock generated
View File

@ -451,6 +451,7 @@ dependencies = [
"serde_json",
"sha2",
"tempdir",
"thiserror",
"trust-dns-client",
]

View File

@ -19,3 +19,4 @@ serde = { version = "1.0.162", features = [ "derive" ]}
serde_json = "1.0.96"
trust-dns-client = "0.22.0"
mockall = "0.11.4"
thiserror = "1.0.40"

View File

@ -7,54 +7,42 @@ pub struct Limits {
// TODO storage limits
}
#[derive(Debug)]
#[derive(thiserror::Error, Debug)]
pub enum ReadFileIntoError {
#[error("file not found")]
FileNotFound,
#[error(transparent)]
Unexpected(Box<dyn Error>),
}
impl<E: Error + 'static> From<E> for ReadFileIntoError {
fn from(e: E) -> ReadFileIntoError {
ReadFileIntoError::Unexpected(Box::from(e))
}
}
#[derive(Debug)]
#[derive(thiserror::Error, Debug)]
pub enum SyncError {
#[error("invalid url")]
InvalidURL,
#[error("invalid branch name")]
InvalidBranchName,
#[error(transparent)]
Unexpected(Box<dyn Error>),
}
impl<E: Error + 'static> From<E> for SyncError {
fn from(e: E) -> SyncError {
SyncError::Unexpected(Box::from(e))
}
}
#[derive(Debug)]
#[derive(thiserror::Error, Debug)]
pub enum GetError {
#[error("not found")]
NotFound,
#[error(transparent)]
Unexpected(Box<dyn Error>),
}
impl<E: Error + 'static> From<E> for GetError {
fn from(e: E) -> GetError {
GetError::Unexpected(Box::from(e))
}
}
#[derive(Debug)]
#[derive(thiserror::Error, Debug)]
pub enum AllDescrsError {
#[error(transparent)]
Unexpected(Box<dyn Error>),
}
impl<E: Error + 'static> From<E> for AllDescrsError {
fn from(e: E) -> AllDescrsError {
AllDescrsError::Unexpected(Box::from(e))
}
}
#[automock]
pub trait Origin {
fn descr(&self) -> &Descr;
@ -80,15 +68,37 @@ pub trait Store<'a> {
fn all_descrs(&'a self) -> AllDescrsResult<Self::AllDescrsIter>;
}
//mockall::mock! {
// pub MyStore {}
// impl<'a> Store<'a> for MyStore {
// type AllDescrsIter = Vec<AllDescrsResult<Descr>>;
// fn sync(&'a self, descr: &Descr, limits: Limits) -> Result<(), SyncError>;
// fn get(&'a self, descr: Descr) -> Result<Box<dyn Origin>, GetError>;
// fn all_descrs(&'a self) -> AllDescrsResult<<self::MockMyStore as self::Store<'a>>::AllDescrsIter>;
// }
//}
pub struct MockStore<SyncFn, GetFn, AllDescrsFn>
where
SyncFn: Fn(&Descr, Limits) -> Result<(), SyncError>,
GetFn: Fn(Descr) -> Result<Box<dyn Origin>, GetError>,
AllDescrsFn: Fn() -> AllDescrsResult<Vec<AllDescrsResult<Descr>>>,
{
pub sync_fn: SyncFn,
pub get_fn: GetFn,
pub all_descrs_fn: AllDescrsFn,
}
impl<'a, SyncFn, GetFn, AllDescrsFn> Store<'a> for MockStore<SyncFn, GetFn, AllDescrsFn>
where
SyncFn: Fn(&Descr, Limits) -> Result<(), SyncError>,
GetFn: Fn(Descr) -> Result<Box<dyn Origin>, GetError>,
AllDescrsFn: Fn() -> AllDescrsResult<Vec<AllDescrsResult<Descr>>>,
{
type AllDescrsIter = Vec<AllDescrsResult<Descr>>;
fn sync(&'a self, descr: &Descr, limits: Limits) -> Result<(), SyncError> {
(self.sync_fn)(descr, limits)
}
fn get(&'a self, descr: Descr) -> Result<Box<dyn Origin>, GetError> {
(self.get_fn)(descr)
}
fn all_descrs(&'a self) -> AllDescrsResult<Self::AllDescrsIter> {
(self.all_descrs_fn)()
}
}
pub mod git {
@ -117,13 +127,18 @@ pub mod git {
let file_object = self
.repo
.find_object(self.tree_object_id)?
.peel_to_tree()?
.lookup_entry_by_path(clean_path)?
.find_object(self.tree_object_id)
.map_err(|e| ReadFileIntoError::Unexpected(Box::from(e)))?
.peel_to_tree()
.map_err(|e| ReadFileIntoError::Unexpected(Box::from(e)))?
.lookup_entry_by_path(clean_path)
.map_err(|e| ReadFileIntoError::Unexpected(Box::from(e)))?
.ok_or(ReadFileIntoError::FileNotFound)?
.object()?;
.object()
.map_err(|e| ReadFileIntoError::Unexpected(Box::from(e)))?;
into.write_all(file_object.data.as_ref())?;
into.write_all(file_object.data.as_ref())
.map_err(|e| ReadFileIntoError::Unexpected(Box::from(e)))?;
Ok(())
}
@ -167,7 +182,7 @@ pub mod git {
// if the path doesn't exist then use the gix clone feature to clone it into the
// directory.
if fs::read_dir(repo_path).is_err() {
fs::create_dir_all(repo_path)?;
fs::create_dir_all(repo_path).map_err(|e| SyncError::Unexpected(Box::from(e)))?;
let Descr::Git { url, branch_name } = descr;
@ -186,30 +201,37 @@ pub mod git {
// Check to make sure the branch name exists
// TODO if this fails we should delete repo_path
repo.try_find_reference(&self.branch_ref(branch_name))?
repo.try_find_reference(&self.branch_ref(branch_name))
.map_err(|e| SyncError::Unexpected(Box::from(e)))?
.ok_or(SyncError::InvalidBranchName)?;
// Add the descr to the repo directory, so we can know the actual descr later
// TODO if this fails we should delete repo_path
let descr_file = fs::File::create(self.descr_file_path(descr.id().as_ref()))?;
let descr_file = fs::File::create(self.descr_file_path(descr.id().as_ref()))
.map_err(|e| SyncError::Unexpected(Box::from(e)))?;
serde_json::to_writer(descr_file, &descr)?;
serde_json::to_writer(descr_file, &descr)
.map_err(|e| SyncError::Unexpected(Box::from(e)))?;
return Ok(());
}
let direction = gix::remote::Direction::Fetch;
let repo = gix::open(repo_path)?;
let repo = gix::open(repo_path).map_err(|e| SyncError::Unexpected(Box::from(e)))?;
let remote = repo
.find_default_remote(direction)
.ok_or_else(|| SyncError::Unexpected(Box::from("no default configured")))??;
.ok_or_else(|| SyncError::Unexpected(Box::from("no default configured")))?
.map_err(|e| SyncError::Unexpected(Box::from(e)))?;
remote
.connect(direction)?
.prepare_fetch(Discard, Default::default())?
.receive(Discard, should_interrupt)?;
.connect(direction)
.map_err(|e| SyncError::Unexpected(Box::from(e)))?
.prepare_fetch(Discard, Default::default())
.map_err(|e| SyncError::Unexpected(Box::from(e)))?
.receive(Discard, should_interrupt)
.map_err(|e| SyncError::Unexpected(Box::from(e)))?;
Ok(())
}
@ -222,19 +244,23 @@ pub mod git {
fs::read_dir(&repo_path).map_err(|e| match e.kind() {
io::ErrorKind::NotFound => GetError::NotFound,
_ => e.into(),
_ => GetError::Unexpected(Box::from(e)),
})?;
let repo = gix::open(&repo_path)?;
let repo = gix::open(&repo_path).map_err(|e| GetError::Unexpected(Box::from(e)))?;
let commit_object_id = repo
.find_reference(&self.branch_ref(branch_name))?
.peel_to_id_in_place()?
.find_reference(&self.branch_ref(branch_name))
.map_err(|e| GetError::Unexpected(Box::from(e)))?
.peel_to_id_in_place()
.map_err(|e| GetError::Unexpected(Box::from(e)))?
.detach();
let tree_object_id = repo
.find_object(commit_object_id)?
.try_to_commit_ref()?
.find_object(commit_object_id)
.map_err(|e| GetError::Unexpected(Box::from(e)))?
.try_to_commit_ref()
.map_err(|e| GetError::Unexpected(Box::from(e)))?
.tree();
return Ok(Box::from(Origin {
@ -245,27 +271,34 @@ pub mod git {
}
fn all_descrs(&'a self) -> AllDescrsResult<Self::AllDescrsIter> {
Ok(Box::from(fs::read_dir(&self.dir_path)?.map(
|dir_entry_res: io::Result<fs::DirEntry>| -> AllDescrsResult<Descr> {
let descr_id: String = dir_entry_res?
.file_name()
.to_str()
.ok_or_else(|| {
AllDescrsError::Unexpected(Box::from(
"couldn't convert os string to &str",
))
})?
.into();
Ok(Box::from(
fs::read_dir(&self.dir_path)
.map_err(|e| AllDescrsError::Unexpected(Box::from(e)))?
.map(
|dir_entry_res: io::Result<fs::DirEntry>| -> AllDescrsResult<Descr> {
let descr_id: String = dir_entry_res
.map_err(|e| AllDescrsError::Unexpected(Box::from(e)))?
.file_name()
.to_str()
.ok_or_else(|| {
AllDescrsError::Unexpected(Box::from(
"couldn't convert os string to &str",
))
})?
.into();
let descr_file_path = self.descr_file_path(descr_id.as_ref());
let descr_file_path = self.descr_file_path(descr_id.as_ref());
let descr_file = fs::File::open(descr_file_path)?;
let descr_file = fs::File::open(descr_file_path)
.map_err(|e| AllDescrsError::Unexpected(Box::from(e)))?;
let descr = serde_json::from_reader(descr_file)?;
let descr = serde_json::from_reader(descr_file)
.map_err(|e| AllDescrsError::Unexpected(Box::from(e)))?;
Ok(descr)
},
)))
Ok(descr)
},
),
))
}
}