refactor Store to return an Origin

This commit is contained in:
Brian Picciano 2023-05-08 13:47:21 +02:00
parent 563f072e09
commit 53e253b362

View File

@ -2,17 +2,19 @@ use super::Descr;
use std::error::Error; use std::error::Error;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct Limits {} pub struct Limits {
// TODO storage limits
}
#[derive(Debug)] #[derive(Debug)]
pub enum WriteFileError { pub enum ReadFileIntoError {
FileNotFound, FileNotFound,
Unexpected(Box<dyn Error>), Unexpected(Box<dyn Error>),
} }
impl<E: Error + 'static> From<E> for WriteFileError { impl<E: Error + 'static> From<E> for ReadFileIntoError {
fn from(e: E) -> WriteFileError { fn from(e: E) -> ReadFileIntoError {
WriteFileError::Unexpected(Box::from(e)) ReadFileIntoError::Unexpected(Box::from(e))
} }
} }
@ -29,6 +31,18 @@ impl<E: Error + 'static> From<E> for SyncError {
} }
} }
#[derive(Debug)]
pub enum GetError {
NotFound,
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(Debug)]
pub enum AllDescrsError { pub enum AllDescrsError {
Unexpected(Box<dyn Error>), Unexpected(Box<dyn Error>),
@ -40,20 +54,25 @@ impl<E: Error + 'static> From<E> for AllDescrsError {
} }
} }
/// Used in the return from all_descrs from Store.
type AllDescrsResult<T> = Result<T, AllDescrsError>; type AllDescrsResult<T> = Result<T, AllDescrsError>;
pub trait Store { pub trait Origin {
fn read_file_into<W>( fn descr(&self) -> &Descr;
fn read_file_into(
&self, &self,
descr: &Descr,
path: &str, path: &str,
into: &mut W, into: &mut dyn std::io::Write,
) -> Result<(), WriteFileError> ) -> Result<(), ReadFileIntoError>;
where }
W: std::io::Write + ?Sized;
/// Describes a storage mechanism for Origins. Each Origin is uniquely identified by its Descr.
pub trait Store {
/// If the origin is of a kind which can be updated, sync will pull down the latest version of
/// the origin into the storage.
fn sync(&self, descr: &Descr, limits: Limits) -> Result<(), SyncError>; fn sync(&self, descr: &Descr, limits: Limits) -> Result<(), SyncError>;
fn get(&self, descr: Descr) -> Result<Box<dyn Origin>, GetError>;
fn all_descrs(&self) -> AllDescrsResult<Box<dyn Iterator<Item = AllDescrsResult<Descr>> + '_>>; fn all_descrs(&self) -> AllDescrsResult<Box<dyn Iterator<Item = AllDescrsResult<Descr>> + '_>>;
} }
@ -63,6 +82,41 @@ pub mod git {
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::{fs, io}; use std::{fs, io};
struct Origin {
descr: Descr,
repo: gix::Repository,
tree_object_id: gix::ObjectId,
}
impl super::Origin for Origin {
fn descr(&self) -> &Descr {
&self.descr
}
fn read_file_into(
&self,
path: &str,
into: &mut dyn std::io::Write,
) -> Result<(), ReadFileIntoError> {
let mut clean_path = Path::new(path);
clean_path = clean_path.strip_prefix("/").unwrap_or(clean_path);
let file_object = self
.repo
.find_object(self.tree_object_id)?
.peel_to_tree()?
.lookup_entry_by_path(clean_path)?
.ok_or(ReadFileIntoError::FileNotFound)?
.object()?;
into.write_all(file_object.data.as_ref())?;
Ok(())
}
}
/// git::Store implements the Store trait for any Descr::Git based Origins. If any non-git
/// Descrs are used then this implementation will panic.
pub struct Store<'a> { pub struct Store<'a> {
dir_path: &'a Path, dir_path: &'a Path,
} }
@ -115,12 +169,12 @@ pub mod git {
.map_err(|_| SyncError::InvalidURL)?; .map_err(|_| SyncError::InvalidURL)?;
// Check to make sure the branch name exists // Check to make sure the branch name exists
// TODO if this fails we should delete the whole repo // 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))?
.ok_or(SyncError::InvalidBranchName)?; .ok_or(SyncError::InvalidBranchName)?;
// Add the descr to the repo directory, so we can know the actual descr later // Add the descr to the repo directory, so we can know the actual descr later
// TODO if this fails we should delete the whole repo // 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()))?;
serde_json::to_writer(descr_file, &descr)?; serde_json::to_writer(descr_file, &descr)?;
@ -144,41 +198,34 @@ pub mod git {
Ok(()) Ok(())
} }
fn read_file_into<W>( fn get(&self, descr: Descr) -> Result<Box<dyn super::Origin>, GetError> {
&self, let repo_path = self.repo_path(&descr);
descr: &Descr, let Descr::Git {
path: &str, ref branch_name, ..
into: &mut W, } = descr;
) -> Result<(), WriteFileError>
where
W: io::Write + ?Sized,
{
let Descr::Git { branch_name, .. } = descr;
let repo_path = &self.repo_path(descr);
let branch_ref = &Self::branch_ref(branch_name);
let mut clean_path = Path::new(path); fs::read_dir(&repo_path).map_err(|e| match e.kind() {
clean_path = clean_path.strip_prefix("/").unwrap_or(clean_path); io::ErrorKind::NotFound => GetError::NotFound,
_ => e.into(),
})?;
let repo = gix::open(repo_path)?; let repo = gix::open(&repo_path)?;
let commit_object_id = repo.find_reference(branch_ref)?.peel_to_id_in_place()?; let commit_object_id = repo
.find_reference(&Self::branch_ref(branch_name))?
.peel_to_id_in_place()?
.detach();
let tree_object_id = repo let tree_object_id = repo
.find_object(commit_object_id)? .find_object(commit_object_id)?
.try_to_commit_ref()? .try_to_commit_ref()?
.tree(); .tree();
let file_object = repo return Ok(Box::from(Origin {
.find_object(tree_object_id)? descr,
.peel_to_tree()? repo,
.lookup_entry_by_path(clean_path)? tree_object_id,
.ok_or(WriteFileError::FileNotFound)? }));
.object()?;
into.write_all(file_object.data.as_ref())?;
Ok(())
} }
fn all_descrs( fn all_descrs(
@ -212,7 +259,7 @@ pub mod git {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::super::Store; use super::super::{Origin, Store};
use super::Descr; use super::Descr;
use tempdir::TempDir; use tempdir::TempDir;
@ -223,10 +270,15 @@ pub mod git {
let curr_dir = format!("file://{}", std::env::current_dir().unwrap().display()); let curr_dir = format!("file://{}", std::env::current_dir().unwrap().display());
let descr = Descr::Git { let descr = Descr::Git {
url: curr_dir, url: curr_dir.clone(),
branch_name: String::from("master"), branch_name: String::from("master"),
}; };
let other_descr = Descr::Git {
url: curr_dir.clone(),
branch_name: String::from("some_other_branch"),
};
let limits = super::Limits {}; let limits = super::Limits {};
let store = super::Store::new(tmp_dir.path()).expect("store created"); let store = super::Store::new(tmp_dir.path()).expect("store created");
@ -236,10 +288,19 @@ pub mod git {
.sync(&descr, limits) .sync(&descr, limits)
.expect("second sync should succeed"); .expect("second sync should succeed");
assert!(matches!(
store.get(other_descr),
Err::<Box<dyn Origin>, super::GetError>(super::GetError::NotFound),
));
let origin = store.get(descr.clone()).expect("origin retrieved");
assert_eq!(&descr, origin.descr());
let assert_write = |path: &str| { let assert_write = |path: &str| {
let mut into: Vec<u8> = vec![]; let mut into: Vec<u8> = vec![];
store origin
.read_file_into(&descr, path, &mut into) .read_file_into(path, &mut into)
.expect("write should succeed"); .expect("write should succeed");
assert!(into.len() > 0); assert!(into.len() > 0);
}; };
@ -250,8 +311,8 @@ pub mod git {
// File doesn't exist // File doesn't exist
let mut into: Vec<u8> = vec![]; let mut into: Vec<u8> = vec![];
assert!(matches!( assert!(matches!(
store.read_file_into(&descr, "DNE", &mut into), origin.read_file_into("DNE", &mut into),
Err::<(), super::WriteFileError>(super::WriteFileError::FileNotFound), Err::<(), super::ReadFileIntoError>(super::ReadFileIntoError::FileNotFound),
)); ));
assert_eq!(into.len(), 0); assert_eq!(into.len(), 0);