diff options
author | Lars Wirzenius <liw@liw.fi> | 2021-02-03 09:11:49 +0200 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2021-02-04 09:14:01 +0200 |
commit | a2adcb5a90c15b473a2fcf114555443fba8a20ce (patch) | |
tree | 7ec36f244daa105b0da774d6705ef736f9135f64 /src | |
parent | bf08ea67ca035fc0e78364450599cefff7cd9bc6 (diff) | |
download | obnam2-a2adcb5a90c15b473a2fcf114555443fba8a20ce.tar.gz |
refactor: have per-module error enums
This means that a function that parses step bindings can't return an
error that the document is missing a title. Such an error return would
be nonsensical, and we use the Rust type system to prevent it, at a
small cost of being a bit verbose.
Additional benefit is that the library portion of Obnam doesn't return
anyhow::Result values anymore.
Diffstat (limited to 'src')
-rw-r--r-- | src/backup_run.rs | 31 | ||||
-rw-r--r-- | src/bin/obnam.rs | 2 | ||||
-rw-r--r-- | src/chunk.rs | 17 | ||||
-rw-r--r-- | src/chunker.rs | 14 | ||||
-rw-r--r-- | src/client.rs | 88 | ||||
-rw-r--r-- | src/cmd/backup.rs | 7 | ||||
-rw-r--r-- | src/cmd/get_chunk.rs | 3 | ||||
-rw-r--r-- | src/cmd/list.rs | 3 | ||||
-rw-r--r-- | src/cmd/list_files.rs | 7 | ||||
-rw-r--r-- | src/cmd/restore.rs | 46 | ||||
-rw-r--r-- | src/cmd/show_gen.rs | 8 | ||||
-rw-r--r-- | src/error.rs | 48 | ||||
-rw-r--r-- | src/fsentry.rs | 17 | ||||
-rw-r--r-- | src/fsiter.rs | 17 | ||||
-rw-r--r-- | src/generation.rs | 83 | ||||
-rw-r--r-- | src/genlist.rs | 12 | ||||
-rw-r--r-- | src/index.rs | 55 | ||||
-rw-r--r-- | src/indexedstore.rs | 43 | ||||
-rw-r--r-- | src/server.rs | 2 | ||||
-rw-r--r-- | src/store.rs | 18 |
20 files changed, 364 insertions, 157 deletions
diff --git a/src/backup_run.rs b/src/backup_run.rs index e3bfc5a..ae3731d 100644 --- a/src/backup_run.rs +++ b/src/backup_run.rs @@ -1,9 +1,10 @@ use crate::backup_progress::BackupProgress; use crate::backup_reason::Reason; use crate::chunkid::ChunkId; -use crate::client::{BackupClient, ClientConfig}; +use crate::client::{BackupClient, ClientConfig, ClientError}; use crate::fsentry::FilesystemEntry; -use crate::generation::LocalGeneration; +use crate::fsiter::{FsIterError, FsIterResult}; +use crate::generation::{LocalGeneration, LocalGenerationError}; use crate::policy::BackupPolicy; use log::{info, warn}; @@ -14,8 +15,22 @@ pub struct BackupRun { progress: BackupProgress, } +#[derive(Debug, thiserror::Error)] +pub enum BackupError { + #[error(transparent)] + ClientError(#[from] ClientError), + + #[error(transparent)] + FsIterError(#[from] FsIterError), + + #[error(transparent)] + LocalGenerationError(#[from] LocalGenerationError), +} + +pub type BackupResult<T> = Result<T, BackupError>; + impl BackupRun { - pub fn new(config: &ClientConfig, buffer_size: usize) -> anyhow::Result<Self> { + pub fn new(config: &ClientConfig, buffer_size: usize) -> BackupResult<Self> { let client = BackupClient::new(&config.server_url)?; let policy = BackupPolicy::new(); let progress = BackupProgress::new(); @@ -37,8 +52,8 @@ impl BackupRun { pub fn backup_file_initially( &self, - entry: anyhow::Result<FilesystemEntry>, - ) -> anyhow::Result<(FilesystemEntry, Vec<ChunkId>, Reason)> { + entry: FsIterResult<FilesystemEntry>, + ) -> BackupResult<(FilesystemEntry, Vec<ChunkId>, Reason)> { match entry { Err(err) => Err(err.into()), Ok(entry) => { @@ -55,14 +70,14 @@ impl BackupRun { pub fn backup_file_incrementally( &self, - entry: anyhow::Result<FilesystemEntry>, + entry: FsIterResult<FilesystemEntry>, old: &LocalGeneration, - ) -> anyhow::Result<(FilesystemEntry, Vec<ChunkId>, Reason)> { + ) -> BackupResult<(FilesystemEntry, Vec<ChunkId>, Reason)> { match entry { Err(err) => { warn!("backup: {}", err); self.progress.found_problem(); - Err(err) + Err(BackupError::FsIterError(err)) } Ok(entry) => { let path = &entry.pathbuf(); diff --git a/src/bin/obnam.rs b/src/bin/obnam.rs index e9f30ca..4e38e1a 100644 --- a/src/bin/obnam.rs +++ b/src/bin/obnam.rs @@ -34,7 +34,7 @@ fn main() -> anyhow::Result<()> { if let Err(ref e) = result { error!("{}", e); eprintln!("ERROR: {}", e); - return result; + result? } info!("client ends successfully"); diff --git a/src/chunk.rs b/src/chunk.rs index 4917b60..a67ed8c 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -30,12 +30,25 @@ pub struct GenerationChunk { chunk_ids: Vec<ChunkId>, } +/// All the errors that may be returned for `GenerationChunk` operations. +#[derive(Debug, thiserror::Error)] +pub enum GenerationChunkError { + #[error(transparent)] + Utf8Error(#[from] std::str::Utf8Error), + + #[error(transparent)] + SerdeJsonError(#[from] serde_json::Error), +} + +/// A result from a chunk operation. +pub type GenerationChunkResult<T> = Result<T, GenerationChunkError>; + impl GenerationChunk { pub fn new(chunk_ids: Vec<ChunkId>) -> Self { Self { chunk_ids } } - pub fn from_data_chunk(chunk: &DataChunk) -> anyhow::Result<Self> { + pub fn from_data_chunk(chunk: &DataChunk) -> GenerationChunkResult<Self> { let data = chunk.data(); let data = std::str::from_utf8(data)?; Ok(serde_json::from_str(data)?) @@ -53,7 +66,7 @@ impl GenerationChunk { self.chunk_ids.iter() } - pub fn to_data_chunk(&self) -> anyhow::Result<DataChunk> { + pub fn to_data_chunk(&self) -> GenerationChunkResult<DataChunk> { let json = serde_json::to_string(self)?; Ok(DataChunk::new(json.as_bytes().to_vec())) } diff --git a/src/chunker.rs b/src/chunker.rs index 145b1db..f424833 100644 --- a/src/chunker.rs +++ b/src/chunker.rs @@ -9,6 +9,14 @@ pub struct Chunker { handle: std::fs::File, } +#[derive(Debug, thiserror::Error)] +pub enum ChunkerError { + #[error(transparent)] + IoError(#[from] std::io::Error), +} + +pub type ChunkerResult<T> = Result<T, ChunkerError>; + impl Chunker { pub fn new(chunk_size: usize, handle: std::fs::File) -> Self { let mut buf = vec![]; @@ -20,7 +28,7 @@ impl Chunker { } } - pub fn read_chunk(&mut self) -> anyhow::Result<Option<(ChunkMeta, DataChunk)>> { + pub fn read_chunk(&mut self) -> ChunkerResult<Option<(ChunkMeta, DataChunk)>> { let mut used = 0; loop { @@ -44,9 +52,9 @@ impl Chunker { } impl Iterator for Chunker { - type Item = anyhow::Result<(ChunkMeta, DataChunk)>; + type Item = ChunkerResult<(ChunkMeta, DataChunk)>; - fn next(&mut self) -> Option<anyhow::Result<(ChunkMeta, DataChunk)>> { + fn next(&mut self) -> Option<ChunkerResult<(ChunkMeta, DataChunk)>> { match self.read_chunk() { Ok(None) => None, Ok(Some((meta, chunk))) => Some(Ok((meta, chunk))), diff --git a/src/client.rs b/src/client.rs index 515b8c9..f9c325c 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,15 +1,13 @@ use crate::checksummer::sha256; use crate::chunk::DataChunk; -use crate::chunk::GenerationChunk; -use crate::chunker::Chunker; +use crate::chunk::{GenerationChunk, GenerationChunkError}; +use crate::chunker::{Chunker, ChunkerError}; use crate::chunkid::ChunkId; use crate::chunkmeta::ChunkMeta; -use crate::error::ObnamError; use crate::fsentry::{FilesystemEntry, FilesystemKind}; -use crate::generation::{FinishedGeneration, LocalGeneration}; +use crate::generation::{FinishedGeneration, LocalGeneration, LocalGenerationError}; use crate::genlist::GenerationList; -use anyhow::Context; use chrono::{DateTime, Local}; use log::{debug, error, info, trace}; use reqwest::blocking::Client; @@ -26,11 +24,21 @@ pub struct ClientConfig { pub log: Option<PathBuf>, } +#[derive(Debug, thiserror::Error)] +pub enum ClientConfigError { + #[error(transparent)] + IoError(#[from] std::io::Error), + + #[error(transparent)] + SerdeYamlError(#[from] serde_yaml::Error), +} + +pub type ClientConfigResult<T> = Result<T, ClientConfigError>; + impl ClientConfig { - pub fn read_config(filename: &Path) -> anyhow::Result<Self> { + pub fn read_config(filename: &Path) -> ClientConfigResult<Self> { trace!("read_config: filename={:?}", filename); - let config = std::fs::read_to_string(filename) - .with_context(|| format!("reading configuration file {}", filename.display()))?; + let config = std::fs::read_to_string(filename)?; let config = serde_yaml::from_str(&config)?; Ok(config) } @@ -46,15 +54,47 @@ pub enum ClientError { #[error("Server does not have generation {0}")] GenerationNotFound(String), + + #[error(transparent)] + GenerationChunkError(#[from] GenerationChunkError), + + #[error(transparent)] + LocalGenerationError(#[from] LocalGenerationError), + + #[error(transparent)] + ChunkerError(#[from] ChunkerError), + + #[error("Server response did not have a 'chunk-meta' header for chunk {0}")] + NoChunkMeta(ChunkId), + + #[error("Wrong checksum for chunk {0}, got {1}, expected {2}")] + WrongChecksum(ChunkId, String, String), + + #[error(transparent)] + ReqwestError(#[from] reqwest::Error), + + #[error(transparent)] + ReqwestToStrError(#[from] reqwest::header::ToStrError), + + #[error(transparent)] + SerdeJsonError(#[from] serde_json::Error), + + #[error(transparent)] + SerdeYamlError(#[from] serde_yaml::Error), + + #[error(transparent)] + IoError(#[from] std::io::Error), } +pub type ClientResult<T> = Result<T, ClientError>; + pub struct BackupClient { client: Client, base_url: String, } impl BackupClient { - pub fn new(base_url: &str) -> anyhow::Result<Self> { + pub fn new(base_url: &str) -> ClientResult<Self> { let client = Client::builder() .danger_accept_invalid_certs(true) .build()?; @@ -68,7 +108,7 @@ impl BackupClient { &self, e: &FilesystemEntry, size: usize, - ) -> anyhow::Result<Vec<ChunkId>> { + ) -> ClientResult<Vec<ChunkId>> { info!("upload entry: {:?}", e); let ids = match e.kind() { FilesystemKind::Regular => self.read_file(e.pathbuf(), size)?, @@ -78,7 +118,7 @@ impl BackupClient { Ok(ids) } - pub fn upload_generation(&self, filename: &Path, size: usize) -> anyhow::Result<ChunkId> { + pub fn upload_generation(&self, filename: &Path, size: usize) -> ClientResult<ChunkId> { info!("upload SQLite {}", filename.display()); let ids = self.read_file(filename.to_path_buf(), size)?; let gen = GenerationChunk::new(ids); @@ -89,7 +129,7 @@ impl BackupClient { Ok(gen_id) } - fn read_file(&self, filename: PathBuf, size: usize) -> anyhow::Result<Vec<ChunkId>> { + fn read_file(&self, filename: PathBuf, size: usize) -> ClientResult<Vec<ChunkId>> { info!("upload file {}", filename.display()); let file = std::fs::File::open(filename)?; let chunker = Chunker::new(size, file); @@ -105,7 +145,7 @@ impl BackupClient { format!("{}/chunks", self.base_url()) } - pub fn has_chunk(&self, meta: &ChunkMeta) -> anyhow::Result<Option<ChunkId>> { + pub fn has_chunk(&self, meta: &ChunkMeta) -> ClientResult<Option<ChunkId>> { trace!("has_chunk: url={:?}", self.base_url()); let req = self .client @@ -136,7 +176,7 @@ impl BackupClient { Ok(has) } - pub fn upload_chunk(&self, meta: ChunkMeta, chunk: DataChunk) -> anyhow::Result<ChunkId> { + pub fn upload_chunk(&self, meta: ChunkMeta, chunk: DataChunk) -> ClientResult<ChunkId> { let res = self .client .post(&self.chunks_url()) @@ -155,11 +195,7 @@ impl BackupClient { Ok(chunk_id) } - pub fn upload_gen_chunk( - &self, - meta: ChunkMeta, - gen: GenerationChunk, - ) -> anyhow::Result<ChunkId> { + pub fn upload_gen_chunk(&self, meta: ChunkMeta, gen: GenerationChunk) -> ClientResult<ChunkId> { let res = self .client .post(&self.chunks_url()) @@ -178,7 +214,7 @@ impl BackupClient { Ok(chunk_id) } - pub fn upload_new_file_chunks(&self, chunker: Chunker) -> anyhow::Result<Vec<ChunkId>> { + pub fn upload_new_file_chunks(&self, chunker: Chunker) -> ClientResult<Vec<ChunkId>> { let mut chunk_ids = vec![]; for item in chunker { let (meta, chunk) = item?; @@ -195,7 +231,7 @@ impl BackupClient { Ok(chunk_ids) } - pub fn list_generations(&self) -> anyhow::Result<GenerationList> { + pub fn list_generations(&self) -> ClientResult<GenerationList> { let url = format!("{}?generation=true", &self.chunks_url()); trace!("list_generations: url={:?}", url); let req = self.client.get(&url).build()?; @@ -212,7 +248,7 @@ impl BackupClient { Ok(GenerationList::new(finished)) } - pub fn fetch_chunk(&self, chunk_id: &ChunkId) -> anyhow::Result<DataChunk> { + pub fn fetch_chunk(&self, chunk_id: &ChunkId) -> ClientResult<DataChunk> { info!("fetch chunk {}", chunk_id); let url = format!("{}/{}", &self.chunks_url(), chunk_id); @@ -227,7 +263,7 @@ impl BackupClient { let headers = res.headers(); let meta = headers.get("chunk-meta"); if meta.is_none() { - let err = ObnamError::NoChunkMeta(chunk_id.clone()); + let err = ClientError::NoChunkMeta(chunk_id.clone()); error!("fetching chunk {} failed: {}", chunk_id, err); return Err(err.into()); } @@ -241,7 +277,7 @@ impl BackupClient { let actual = sha256(&body); if actual != meta.sha256() { let err = - ObnamError::WrongChecksum(chunk_id.clone(), actual, meta.sha256().to_string()); + ClientError::WrongChecksum(chunk_id.clone(), actual, meta.sha256().to_string()); error!("fetching chunk {} failed: {}", chunk_id, err); return Err(err.into()); } @@ -251,14 +287,14 @@ impl BackupClient { Ok(chunk) } - fn fetch_generation_chunk(&self, gen_id: &str) -> anyhow::Result<GenerationChunk> { + fn fetch_generation_chunk(&self, gen_id: &str) -> ClientResult<GenerationChunk> { let chunk_id = ChunkId::from_str(gen_id); let chunk = self.fetch_chunk(&chunk_id)?; let gen = GenerationChunk::from_data_chunk(&chunk)?; Ok(gen) } - pub fn fetch_generation(&self, gen_id: &str, dbname: &Path) -> anyhow::Result<LocalGeneration> { + pub fn fetch_generation(&self, gen_id: &str, dbname: &Path) -> ClientResult<LocalGeneration> { let gen = self.fetch_generation_chunk(gen_id)?; // Fetch the SQLite file, storing it in the named file. diff --git a/src/cmd/backup.rs b/src/cmd/backup.rs index da7298f..a43a622 100644 --- a/src/cmd/backup.rs +++ b/src/cmd/backup.rs @@ -1,12 +1,13 @@ use crate::backup_run::BackupRun; use crate::client::ClientConfig; +use crate::error::ObnamError; use crate::fsiter::FsIterator; use crate::generation::NascentGeneration; use log::info; use std::time::SystemTime; use tempfile::NamedTempFile; -pub fn backup(config: &ClientConfig, buffer_size: usize) -> anyhow::Result<()> { +pub fn backup(config: &ClientConfig, buffer_size: usize) -> Result<(), ObnamError> { let runtime = SystemTime::now(); let run = BackupRun::new(config, buffer_size)?; @@ -33,11 +34,11 @@ pub fn backup(config: &ClientConfig, buffer_size: usize) -> anyhow::Result<()> { let mut new = NascentGeneration::create(&newname)?; match genlist.resolve("latest") { - None => { + Err(_) => { info!("fresh backup without a previous generation"); new.insert_iter(iter.map(|entry| run.backup_file_initially(entry)))?; } - Some(old) => { + Ok(old) => { info!("incremental backup based on {}", old); let old = run.client().fetch_generation(&old, &oldname)?; run.progress() diff --git a/src/cmd/get_chunk.rs b/src/cmd/get_chunk.rs index bf653ff..c1d7590 100644 --- a/src/cmd/get_chunk.rs +++ b/src/cmd/get_chunk.rs @@ -1,9 +1,10 @@ use crate::chunkid::ChunkId; use crate::client::BackupClient; use crate::client::ClientConfig; +use crate::error::ObnamError; use std::io::{stdout, Write}; -pub fn get_chunk(config: &ClientConfig, chunk_id: &str) -> anyhow::Result<()> { +pub fn get_chunk(config: &ClientConfig, chunk_id: &str) -> Result<(), ObnamError> { let client = BackupClient::new(&config.server_url)?; let chunk_id: ChunkId = chunk_id.parse().unwrap(); let chunk = client.fetch_chunk(&chunk_id)?; diff --git a/src/cmd/list.rs b/src/cmd/list.rs index 8766e34..ce19a72 100644 --- a/src/cmd/list.rs +++ b/src/cmd/list.rs @@ -1,6 +1,7 @@ use crate::client::{BackupClient, ClientConfig}; +use crate::error::ObnamError; -pub fn list(config: &ClientConfig) -> anyhow::Result<()> { +pub fn list(config: &ClientConfig) -> Result<(), ObnamError> { let client = BackupClient::new(&config.server_url)?; let generations = client.list_generations()?; diff --git a/src/cmd/list_files.rs b/src/cmd/list_files.rs index a69c3df..b240d5a 100644 --- a/src/cmd/list_files.rs +++ b/src/cmd/list_files.rs @@ -5,7 +5,7 @@ use crate::error::ObnamError; use crate::fsentry::{FilesystemEntry, FilesystemKind}; use tempfile::NamedTempFile; -pub fn list_files(config: &ClientConfig, gen_ref: &str) -> anyhow::Result<()> { +pub fn list_files(config: &ClientConfig, gen_ref: &str) -> Result<(), ObnamError> { // Create a named temporary file. We don't meed the open file // handle, so we discard that. let dbname = { @@ -17,10 +17,7 @@ pub fn list_files(config: &ClientConfig, gen_ref: &str) -> anyhow::Result<()> { let client = BackupClient::new(&config.server_url)?; let genlist = client.list_generations()?; - let gen_id: String = match genlist.resolve(gen_ref) { - None => return Err(ObnamError::UnknownGeneration(gen_ref.to_string()).into()), - Some(id) => id, - }; + let gen_id: String = genlist.resolve(gen_ref)?; let gen = client.fetch_generation(&gen_id, &dbname)?; for file in gen.files()? { diff --git a/src/cmd/restore.rs b/src/cmd/restore.rs index d783a70..16d5320 100644 --- a/src/cmd/restore.rs +++ b/src/cmd/restore.rs @@ -1,8 +1,8 @@ -use crate::client::BackupClient; use crate::client::ClientConfig; +use crate::client::{BackupClient, ClientError}; use crate::error::ObnamError; use crate::fsentry::{FilesystemEntry, FilesystemKind}; -use crate::generation::LocalGeneration; +use crate::generation::{LocalGeneration, LocalGenerationError}; use indicatif::{ProgressBar, ProgressStyle}; use libc::{fchmod, futimens, timespec}; use log::{debug, error, info}; @@ -11,11 +11,12 @@ use std::io::prelude::*; use std::io::Error; use std::os::unix::fs::symlink; use std::os::unix::io::AsRawFd; +use std::path::StripPrefixError; use std::path::{Path, PathBuf}; use structopt::StructOpt; use tempfile::NamedTempFile; -pub fn restore(config: &ClientConfig, gen_ref: &str, to: &Path) -> anyhow::Result<()> { +pub fn restore(config: &ClientConfig, gen_ref: &str, to: &Path) -> Result<(), ObnamError> { // Create a named temporary file. We don't meed the open file // handle, so we discard that. let dbname = { @@ -27,10 +28,7 @@ pub fn restore(config: &ClientConfig, gen_ref: &str, to: &Path) -> anyhow::Resul let client = BackupClient::new(&config.server_url)?; let genlist = client.list_generations()?; - let gen_id: String = match genlist.resolve(gen_ref) { - None => return Err(ObnamError::UnknownGeneration(gen_ref.to_string()).into()), - Some(id) => id, - }; + let gen_id: String = genlist.resolve(gen_ref)?; info!("generation id is {}", gen_id); let gen = client.fetch_generation(&gen_id, &dbname)?; @@ -68,6 +66,26 @@ struct Opt { to: PathBuf, } +#[derive(Debug, thiserror::Error)] +pub enum RestoreError { + #[error(transparent)] + ClientError(#[from] ClientError), + + #[error(transparent)] + LocalGenerationError(#[from] LocalGenerationError), + + #[error(transparent)] + StripPrefixError(#[from] StripPrefixError), + + #[error(transparent)] + IoError(#[from] std::io::Error), + + #[error(transparent)] + SerdeYamlError(#[from] serde_yaml::Error), +} + +pub type RestoreResult<T> = Result<T, RestoreError>; + fn restore_generation( client: &BackupClient, gen: &LocalGeneration, @@ -75,7 +93,7 @@ fn restore_generation( entry: &FilesystemEntry, to: &Path, progress: &ProgressBar, -) -> anyhow::Result<()> { +) -> RestoreResult<()> { info!("restoring {:?}", entry); progress.set_message(&format!("{}", entry.pathbuf().display())); progress.inc(1); @@ -89,13 +107,13 @@ fn restore_generation( Ok(()) } -fn restore_directory(path: &Path) -> anyhow::Result<()> { +fn restore_directory(path: &Path) -> RestoreResult<()> { debug!("restoring directory {}", path.display()); std::fs::create_dir_all(path)?; Ok(()) } -fn restore_directory_metadata(entry: &FilesystemEntry, to: &Path) -> anyhow::Result<()> { +fn restore_directory_metadata(entry: &FilesystemEntry, to: &Path) -> RestoreResult<()> { let to = restored_path(entry, to)?; match entry.kind() { FilesystemKind::Directory => restore_metadata(&to, entry)?, @@ -107,7 +125,7 @@ fn restore_directory_metadata(entry: &FilesystemEntry, to: &Path) -> anyhow::Res Ok(()) } -fn restored_path(entry: &FilesystemEntry, to: &Path) -> anyhow::Result<PathBuf> { +fn restored_path(entry: &FilesystemEntry, to: &Path) -> RestoreResult<PathBuf> { let path = &entry.pathbuf(); let path = if path.is_absolute() { path.strip_prefix("/")? @@ -123,7 +141,7 @@ fn restore_regular( path: &Path, fileid: i64, entry: &FilesystemEntry, -) -> anyhow::Result<()> { +) -> RestoreResult<()> { debug!("restoring regular {}", path.display()); let parent = path.parent().unwrap(); debug!(" mkdir {}", parent.display()); @@ -140,7 +158,7 @@ fn restore_regular( Ok(()) } -fn restore_symlink(path: &Path, entry: &FilesystemEntry) -> anyhow::Result<()> { +fn restore_symlink(path: &Path, entry: &FilesystemEntry) -> RestoreResult<()> { debug!("restoring symlink {}", path.display()); let parent = path.parent().unwrap(); debug!(" mkdir {}", parent.display()); @@ -154,7 +172,7 @@ fn restore_symlink(path: &Path, entry: &FilesystemEntry) -> anyhow::Result<()> { Ok(()) } -fn restore_metadata(path: &Path, entry: &FilesystemEntry) -> anyhow::Result<()> { +fn restore_metadata(path: &Path, entry: &FilesystemEntry) -> RestoreResult<()> { debug!("restoring metadata for {}", entry.pathbuf().display()); let handle = File::open(path)?; diff --git a/src/cmd/show_gen.rs b/src/cmd/show_gen.rs index d355389..3dcdbf2 100644 --- a/src/cmd/show_gen.rs +++ b/src/cmd/show_gen.rs @@ -5,7 +5,7 @@ use crate::fsentry::FilesystemKind; use indicatif::HumanBytes; use tempfile::NamedTempFile; -pub fn show_generation(config: &ClientConfig, gen_ref: &str) -> anyhow::Result<()> { +pub fn show_generation(config: &ClientConfig, gen_ref: &str) -> Result<(), ObnamError> { // Create a named temporary file. We don't meed the open file // handle, so we discard that. let dbname = { @@ -17,11 +17,7 @@ pub fn show_generation(config: &ClientConfig, gen_ref: &str) -> anyhow::Result<( let client = BackupClient::new(&config.server_url)?; let genlist = client.list_generations()?; - let gen_id: String = match genlist.resolve(gen_ref) { - None => return Err(ObnamError::UnknownGeneration(gen_ref.to_string()).into()), - Some(id) => id, - }; - + let gen_id: String = genlist.resolve(gen_ref)?; let gen = client.fetch_generation(&gen_id, &dbname)?; let files = gen.files()?; diff --git a/src/error.rs b/src/error.rs index d368763..cf286db 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,25 +1,43 @@ -use crate::chunkid::ChunkId; -use std::path::PathBuf; +use crate::backup_run::BackupError; +use crate::client::{ClientConfigError, ClientError}; +use crate::cmd::restore::RestoreError; +use crate::generation::{LocalGenerationError, NascentError}; +use crate::genlist::GenerationListError; +use std::time::SystemTimeError; +use tempfile::PersistError; use thiserror::Error; -/// Define all the kinds of errors any part of this crate can return. +/// Define all the kinds of errors that functions corresponding to +/// subcommands of the main program can return. #[derive(Debug, Error)] pub enum ObnamError { - #[error("Can't find backup '{0}'")] - UnknownGeneration(String), + #[error(transparent)] + GenerationListError(#[from] GenerationListError), - #[error("Generation has more than one file with the name {0}")] - TooManyFiles(PathBuf), + #[error(transparent)] + ClientError(#[from] ClientError), - #[error("Server response did not have a 'chunk-meta' header for chunk {0}")] - NoChunkMeta(ChunkId), + #[error(transparent)] + ClientConfigError(#[from] ClientConfigError), - #[error("Wrong checksum for chunk {0}, got {1}, expected {2}")] - WrongChecksum(ChunkId, String, String), + #[error(transparent)] + BackupError(#[from] BackupError), - #[error("Chunk is missing: {0}")] - MissingChunk(ChunkId), + #[error(transparent)] + NascentError(#[from] NascentError), - #[error("Chunk is in store too many times: {0}")] - DuplicateChunk(ChunkId), + #[error(transparent)] + LocalGenerationError(#[from] LocalGenerationError), + + #[error(transparent)] + RestoreError(#[from] RestoreError), + + #[error(transparent)] + PersistError(#[from] PersistError), + + #[error(transparent)] + IoError(#[from] std::io::Error), + + #[error(transparent)] + SystemTimeError(#[from] SystemTimeError), } diff --git a/src/fsentry.rs b/src/fsentry.rs index eae11b4..89c1cc0 100644 --- a/src/fsentry.rs +++ b/src/fsentry.rs @@ -37,9 +37,20 @@ pub struct FilesystemEntry { symlink_target: Option<PathBuf>, } +#[derive(Debug, thiserror::Error)] +pub enum FsEntryError { + #[error("Unknown file kind {0}")] + UnknownFileKindCode(u8), + + #[error(transparent)] + IoError(#[from] std::io::Error), +} + +pub type FsEntryResult<T> = Result<T, FsEntryError>; + #[allow(clippy::len_without_is_empty)] impl FilesystemEntry { - pub fn from_metadata(path: &Path, meta: &Metadata) -> anyhow::Result<Self> { + pub fn from_metadata(path: &Path, meta: &Metadata) -> FsEntryResult<Self> { let kind = FilesystemKind::from_file_type(meta.file_type()); Ok(Self { path: path.to_path_buf().into_os_string().into_vec(), @@ -129,12 +140,12 @@ impl FilesystemKind { } } - pub fn from_code(code: u8) -> anyhow::Result<Self> { + pub fn from_code(code: u8) -> FsEntryResult<Self> { match code { 0 => Ok(FilesystemKind::Regular), 1 => Ok(FilesystemKind::Directory), 2 => Ok(FilesystemKind::Symlink), - _ => Err(Error::UnknownFileKindCode(code).into()), + _ => Err(FsEntryError::UnknownFileKindCode(code).into()), } } } diff --git a/src/fsiter.rs b/src/fsiter.rs index a40ad34..36693a6 100644 --- a/src/fsiter.rs +++ b/src/fsiter.rs @@ -1,4 +1,4 @@ -use crate::fsentry::FilesystemEntry; +use crate::fsentry::{FilesystemEntry, FsEntryError}; use log::info; use std::path::Path; use walkdir::{IntoIter, WalkDir}; @@ -8,6 +8,17 @@ pub struct FsIterator { iter: IntoIter, } +#[derive(Debug, thiserror::Error)] +pub enum FsIterError { + #[error(transparent)] + WalkError(#[from] walkdir::Error), + + #[error(transparent)] + FsEntryError(#[from] FsEntryError), +} + +pub type FsIterResult<T> = Result<T, FsIterError>; + impl FsIterator { pub fn new(root: &Path) -> Self { Self { @@ -17,7 +28,7 @@ impl FsIterator { } impl Iterator for FsIterator { - type Item = Result<FilesystemEntry, anyhow::Error>; + type Item = FsIterResult<FilesystemEntry>; fn next(&mut self) -> Option<Self::Item> { match self.iter.next() { None => None, @@ -30,7 +41,7 @@ impl Iterator for FsIterator { } } -fn new_entry(e: &walkdir::DirEntry) -> anyhow::Result<FilesystemEntry> { +fn new_entry(e: &walkdir::DirEntry) -> FsIterResult<FilesystemEntry> { let meta = e.metadata()?; let entry = FilesystemEntry::from_metadata(e.path(), &meta)?; Ok(entry) diff --git a/src/generation.rs b/src/generation.rs index 8a15363..4655c17 100644 --- a/src/generation.rs +++ b/src/generation.rs @@ -1,8 +1,9 @@ use crate::backup_reason::Reason; +use crate::backup_run::{BackupError, BackupResult}; use crate::chunkid::ChunkId; use crate::fsentry::FilesystemEntry; use rusqlite::Connection; -use std::path::Path; +use std::path::{Path, PathBuf}; /// An identifier for a file in a generation. type FileId = i64; @@ -17,8 +18,25 @@ pub struct NascentGeneration { fileno: FileId, } +#[derive(Debug, thiserror::Error)] +pub enum NascentError { + #[error(transparent)] + LocalGenerationError(#[from] LocalGenerationError), + + #[error(transparent)] + BackupError(#[from] BackupError), + + #[error(transparent)] + RusqliteError(#[from] rusqlite::Error), + + #[error(transparent)] + IoError(#[from] std::io::Error), +} + +pub type NascentResult<T> = Result<T, NascentError>; + impl NascentGeneration { - pub fn create<P>(filename: P) -> anyhow::Result<Self> + pub fn create<P>(filename: P) -> NascentResult<Self> where P: AsRef<Path>, { @@ -35,7 +53,7 @@ impl NascentGeneration { e: FilesystemEntry, ids: &[ChunkId], reason: Reason, - ) -> anyhow::Result<()> { + ) -> NascentResult<()> { let t = self.conn.transaction()?; self.fileno += 1; sql::insert_one(&t, e, self.fileno, ids, reason)?; @@ -45,8 +63,8 @@ impl NascentGeneration { pub fn insert_iter<'a>( &mut self, - entries: impl Iterator<Item = anyhow::Result<(FilesystemEntry, Vec<ChunkId>, Reason)>>, - ) -> anyhow::Result<()> { + entries: impl Iterator<Item = BackupResult<(FilesystemEntry, Vec<ChunkId>, Reason)>>, + ) -> NascentResult<()> { let t = self.conn.transaction()?; for r in entries { let (e, ids, reason) = r?; @@ -110,6 +128,23 @@ pub struct LocalGeneration { conn: Connection, } +#[derive(Debug, thiserror::Error)] +pub enum LocalGenerationError { + #[error("Generation has more than one file with the name {0}")] + TooManyFiles(PathBuf), + + #[error(transparent)] + RusqliteError(#[from] rusqlite::Error), + + #[error(transparent)] + SerdeJsonError(#[from] serde_json::Error), + + #[error(transparent)] + IoError(#[from] std::io::Error), +} + +pub type LocalGenerationResult<T> = Result<T, LocalGenerationError>; + pub struct BackedUpFile { fileno: FileId, entry: FilesystemEntry, @@ -140,7 +175,7 @@ impl BackedUpFile { } impl LocalGeneration { - pub fn open<P>(filename: P) -> anyhow::Result<Self> + pub fn open<P>(filename: P) -> LocalGenerationResult<Self> where P: AsRef<Path>, { @@ -148,23 +183,23 @@ impl LocalGeneration { Ok(Self { conn }) } - pub fn file_count(&self) -> anyhow::Result<i64> { + pub fn file_count(&self) -> LocalGenerationResult<i64> { Ok(sql::file_count(&self.conn)?) } - pub fn files(&self) -> anyhow::Result<Vec<BackedUpFile>> { + pub fn files(&self) -> LocalGenerationResult<Vec<BackedUpFile>> { Ok(sql::files(&self.conn)?) } - pub fn chunkids(&self, fileno: FileId) -> anyhow::Result<Vec<ChunkId>> { + pub fn chunkids(&self, fileno: FileId) -> LocalGenerationResult<Vec<ChunkId>> { Ok(sql::chunkids(&self.conn, fileno)?) } - pub fn get_file(&self, filename: &Path) -> anyhow::Result<Option<FilesystemEntry>> { + pub fn get_file(&self, filename: &Path) -> LocalGenerationResult<Option<FilesystemEntry>> { Ok(sql::get_file(&self.conn, filename)?) } - pub fn get_fileno(&self, filename: &Path) -> anyhow::Result<Option<FileId>> { + pub fn get_fileno(&self, filename: &Path) -> LocalGenerationResult<Option<FileId>> { Ok(sql::get_fileno(&self.conn, filename)?) } } @@ -172,15 +207,16 @@ impl LocalGeneration { mod sql { use super::BackedUpFile; use super::FileId; + use super::LocalGenerationError; + use super::LocalGenerationResult; use crate::backup_reason::Reason; use crate::chunkid::ChunkId; - use crate::error::ObnamError; use crate::fsentry::FilesystemEntry; use rusqlite::{params, Connection, OpenFlags, Row, Transaction}; use std::os::unix::ffi::OsStrExt; use std::path::Path; - pub fn create_db(filename: &Path) -> anyhow::Result<Connection> { + pub fn create_db(filename: &Path) -> LocalGenerationResult<Connection> { let flags = OpenFlags::SQLITE_OPEN_CREATE | OpenFlags::SQLITE_OPEN_READ_WRITE; let conn = Connection::open_with_flags(filename, flags)?; conn.execute( @@ -197,7 +233,7 @@ mod sql { Ok(conn) } - pub fn open_db(filename: &Path) -> anyhow::Result<Connection> { + pub fn open_db(filename: &Path) -> LocalGenerationResult<Connection> { let flags = OpenFlags::SQLITE_OPEN_READ_WRITE; let conn = Connection::open_with_flags(filename, flags)?; conn.pragma_update(None, "journal_mode", &"WAL")?; @@ -210,7 +246,7 @@ mod sql { fileno: FileId, ids: &[ChunkId], reason: Reason, - ) -> anyhow::Result<()> { + ) -> LocalGenerationResult<()> { let json = serde_json::to_string(&e)?; t.execute( "INSERT INTO files (fileno, filename, json, reason) VALUES (?1, ?2, ?3, ?4)", @@ -236,7 +272,7 @@ mod sql { Ok((fileno, json, reason)) } - pub fn file_count(conn: &Connection) -> anyhow::Result<FileId> { + pub fn file_count(conn: &Connection) -> LocalGenerationResult<FileId> { let mut stmt = conn.prepare("SELECT count(*) FROM files")?; let mut iter = stmt.query_map(params![], |row| row.get(0))?; let count = iter.next().expect("SQL count result (1)"); @@ -244,7 +280,7 @@ mod sql { Ok(count) } - pub fn files(conn: &Connection) -> anyhow::Result<Vec<BackedUpFile>> { + pub fn files(conn: &Connection) -> LocalGenerationResult<Vec<BackedUpFile>> { let mut stmt = conn.prepare("SELECT * FROM files")?; let iter = stmt.query_map(params![], |row| row_to_entry(row))?; let mut files = vec![]; @@ -256,7 +292,7 @@ mod sql { Ok(files) } - pub fn chunkids(conn: &Connection, fileno: FileId) -> anyhow::Result<Vec<ChunkId>> { + pub fn chunkids(conn: &Connection, fileno: FileId) -> LocalGenerationResult<Vec<ChunkId>> { let mut stmt = conn.prepare("SELECT chunkid FROM chunks WHERE fileno = ?1")?; let iter = stmt.query_map(params![fileno], |row| Ok(row.get(0)?))?; let mut ids: Vec<ChunkId> = vec![]; @@ -267,14 +303,17 @@ mod sql { Ok(ids) } - pub fn get_file(conn: &Connection, filename: &Path) -> anyhow::Result<Option<FilesystemEntry>> { + pub fn get_file( + conn: &Connection, + filename: &Path, + ) -> LocalGenerationResult<Option<FilesystemEntry>> { match get_file_and_fileno(conn, filename)? { None => Ok(None), Some((_, e, _)) => Ok(Some(e)), } } - pub fn get_fileno(conn: &Connection, filename: &Path) -> anyhow::Result<Option<FileId>> { + pub fn get_fileno(conn: &Connection, filename: &Path) -> LocalGenerationResult<Option<FileId>> { match get_file_and_fileno(conn, filename)? { None => Ok(None), Some((id, _, _)) => Ok(Some(id)), @@ -284,7 +323,7 @@ mod sql { fn get_file_and_fileno( conn: &Connection, filename: &Path, - ) -> anyhow::Result<Option<(FileId, FilesystemEntry, String)>> { + ) -> LocalGenerationResult<Option<(FileId, FilesystemEntry, String)>> { let mut stmt = conn.prepare("SELECT * FROM files WHERE filename = ?1")?; let mut iter = stmt.query_map(params![path_into_blob(filename)], |row| row_to_entry(row))?; @@ -296,7 +335,7 @@ mod sql { if iter.next() == None { Ok(Some((fileno, entry, reason))) } else { - Err(ObnamError::TooManyFiles(filename.to_path_buf()).into()) + Err(LocalGenerationError::TooManyFiles(filename.to_path_buf()).into()) } } } diff --git a/src/genlist.rs b/src/genlist.rs index 10c614e..5eec248 100644 --- a/src/genlist.rs +++ b/src/genlist.rs @@ -5,6 +5,12 @@ pub struct GenerationList { list: Vec<FinishedGeneration>, } +#[derive(Debug, thiserror::Error)] +pub enum GenerationListError { + #[error("Unknown generation: {0}")] + UnknownGeneration(String), +} + impl GenerationList { pub fn new(gens: Vec<FinishedGeneration>) -> Self { let mut list = gens.clone(); @@ -16,7 +22,7 @@ impl GenerationList { self.list.iter() } - pub fn resolve(&self, genref: &str) -> Option<String> { + pub fn resolve(&self, genref: &str) -> Result<String, GenerationListError> { let gen = if self.list.is_empty() { None } else if genref == "latest" { @@ -36,8 +42,8 @@ impl GenerationList { } }; match gen { - None => None, - Some(gen) => Some(gen.id().to_string()), + None => Err(GenerationListError::UnknownGeneration(genref.to_string())), + Some(gen) => Ok(gen.id().to_string()), } } } diff --git a/src/index.rs b/src/index.rs index d527839..f7300da 100644 --- a/src/index.rs +++ b/src/index.rs @@ -17,8 +17,27 @@ pub struct Index { metas: HashMap<ChunkId, ChunkMeta>, } +/// All the errors that may be returned for `Index`. +#[derive(Debug, thiserror::Error)] +pub enum IndexError { + /// Index does not have a chunk. + #[error("The repository index does not have chunk {0}")] + MissingChunk(ChunkId), + + /// Index has chunk more than once. + #[error("The repository index duplicates chunk {0}")] + DuplicateChunk(ChunkId), + + /// An error from SQLite. + #[error(transparent)] + SqlError(#[from] rusqlite::Error), +} + +/// A result from an `Index` operation. +pub type IndexResult<T> = Result<T, IndexError>; + impl Index { - pub fn new<P: AsRef<Path>>(dirname: P) -> anyhow::Result<Self> { + pub fn new<P: AsRef<Path>>(dirname: P) -> IndexResult<Self> { let filename = dirname.as_ref().join("meta.db"); let conn = if filename.exists() { sql::open_db(&filename)? @@ -34,26 +53,26 @@ impl Index { }) } - pub fn insert_meta(&mut self, id: ChunkId, meta: ChunkMeta) -> anyhow::Result<()> { + pub fn insert_meta(&mut self, id: ChunkId, meta: ChunkMeta) -> IndexResult<()> { let t = self.conn.transaction()?; sql::insert(&t, &id, &meta)?; t.commit()?; Ok(()) } - pub fn get_meta(&self, id: &ChunkId) -> anyhow::Result<ChunkMeta> { + pub fn get_meta(&self, id: &ChunkId) -> IndexResult<ChunkMeta> { sql::lookup(&self.conn, id) } - pub fn remove_meta(&mut self, id: &ChunkId) -> anyhow::Result<()> { + pub fn remove_meta(&mut self, id: &ChunkId) -> IndexResult<()> { sql::remove(&self.conn, id) } - pub fn find_by_sha256(&self, sha256: &str) -> anyhow::Result<Vec<ChunkId>> { + pub fn find_by_sha256(&self, sha256: &str) -> IndexResult<Vec<ChunkId>> { sql::find_by_256(&self.conn, sha256) } - pub fn find_generations(&self) -> anyhow::Result<Vec<ChunkId>> { + pub fn find_generations(&self) -> IndexResult<Vec<ChunkId>> { sql::find_generations(&self.conn) } } @@ -132,14 +151,14 @@ mod test { } mod sql { + use super::{IndexError, IndexResult}; use crate::chunkid::ChunkId; use crate::chunkmeta::ChunkMeta; - use crate::error::ObnamError; use log::error; use rusqlite::{params, Connection, OpenFlags, Row, Transaction}; use std::path::Path; - pub fn create_db(filename: &Path) -> anyhow::Result<Connection> { + pub fn create_db(filename: &Path) -> IndexResult<Connection> { let flags = OpenFlags::SQLITE_OPEN_CREATE | OpenFlags::SQLITE_OPEN_READ_WRITE; let conn = Connection::open_with_flags(filename, flags)?; conn.execute( @@ -155,14 +174,14 @@ mod sql { Ok(conn) } - pub fn open_db(filename: &Path) -> anyhow::Result<Connection> { + pub fn open_db(filename: &Path) -> IndexResult<Connection> { let flags = OpenFlags::SQLITE_OPEN_READ_WRITE; let conn = Connection::open_with_flags(filename, flags)?; conn.pragma_update(None, "journal_mode", &"WAL")?; Ok(conn) } - pub fn insert(t: &Transaction, chunkid: &ChunkId, meta: &ChunkMeta) -> anyhow::Result<()> { + pub fn insert(t: &Transaction, chunkid: &ChunkId, meta: &ChunkMeta) -> IndexResult<()> { let chunkid = format!("{}", chunkid); let sha256 = meta.sha256(); let generation = if meta.is_generation() { 1 } else { 0 }; @@ -174,12 +193,12 @@ mod sql { Ok(()) } - pub fn remove(conn: &Connection, chunkid: &ChunkId) -> anyhow::Result<()> { + pub fn remove(conn: &Connection, chunkid: &ChunkId) -> IndexResult<()> { conn.execute("DELETE FROM chunks WHERE id IS ?1", params![chunkid])?; Ok(()) } - pub fn lookup(conn: &Connection, id: &ChunkId) -> anyhow::Result<ChunkMeta> { + pub fn lookup(conn: &Connection, id: &ChunkId) -> IndexResult<ChunkMeta> { let mut stmt = conn.prepare("SELECT * FROM chunks WHERE id IS ?1")?; let iter = stmt.query_map(params![id], |row| row_to_meta(row))?; let mut metas: Vec<ChunkMeta> = vec![]; @@ -189,20 +208,20 @@ mod sql { eprintln!("lookup: meta={:?}", meta); metas.push(meta); } else { - let err = ObnamError::DuplicateChunk(id.clone()); + let err = IndexError::DuplicateChunk(id.clone()); error!("{}", err); return Err(err.into()); } } if metas.len() == 0 { eprintln!("lookup: no hits"); - return Err(ObnamError::MissingChunk(id.clone()).into()); + return Err(IndexError::MissingChunk(id.clone()).into()); } let r = metas[0].clone(); Ok(r) } - pub fn find_by_256(conn: &Connection, sha256: &str) -> anyhow::Result<Vec<ChunkId>> { + pub fn find_by_256(conn: &Connection, sha256: &str) -> IndexResult<Vec<ChunkId>> { let mut stmt = conn.prepare("SELECT id FROM chunks WHERE sha256 IS ?1")?; let iter = stmt.query_map(params![sha256], |row| row_to_id(row))?; let mut ids = vec![]; @@ -213,7 +232,7 @@ mod sql { Ok(ids) } - pub fn find_generations(conn: &Connection) -> anyhow::Result<Vec<ChunkId>> { + pub fn find_generations(conn: &Connection) -> IndexResult<Vec<ChunkId>> { let mut stmt = conn.prepare("SELECT id FROM chunks WHERE generation IS 1")?; let iter = stmt.query_map(params![], |row| row_to_id(row))?; let mut ids = vec![]; @@ -224,7 +243,7 @@ mod sql { Ok(ids) } - pub fn row_to_meta(row: &Row) -> rusqlite::Result<ChunkMeta> { + fn row_to_meta(row: &Row) -> rusqlite::Result<ChunkMeta> { let sha256: String = row.get(row.column_index("sha256")?)?; let generation: i32 = row.get(row.column_index("generation")?)?; let meta = if generation == 0 { @@ -236,7 +255,7 @@ mod sql { Ok(meta) } - pub fn row_to_id(row: &Row) -> rusqlite::Result<ChunkId> { + fn row_to_id(row: &Row) -> rusqlite::Result<ChunkId> { let id: String = row.get(row.column_index("id")?)?; Ok(ChunkId::from_str(&id)) } diff --git a/src/indexedstore.rs b/src/indexedstore.rs index 0366013..3f347dd 100644 --- a/src/indexedstore.rs +++ b/src/indexedstore.rs @@ -1,8 +1,8 @@ use crate::chunk::DataChunk; use crate::chunkid::ChunkId; use crate::chunkmeta::ChunkMeta; -use crate::index::Index; -use crate::store::Store; +use crate::index::{Index, IndexError}; +use crate::store::{Store, StoreError}; use std::path::Path; /// A store for chunks and their metadata. @@ -14,43 +14,58 @@ pub struct IndexedStore { index: Index, } +/// All the errors that may be returned for `IndexStore`. +#[derive(Debug, thiserror::Error)] +pub enum IndexedError { + /// An error from Index. + #[error(transparent)] + IndexError(#[from] IndexError), + + /// An error from Store. + #[error(transparent)] + SqlError(#[from] StoreError), +} + +/// A result from an `Index` operation. +pub type IndexedResult<T> = Result<T, IndexedError>; + impl IndexedStore { - pub fn new(dirname: &Path) -> anyhow::Result<Self> { + pub fn new(dirname: &Path) -> IndexedResult<Self> { let store = Store::new(dirname); let index = Index::new(dirname)?; Ok(Self { store, index }) } - pub fn save(&mut self, meta: &ChunkMeta, chunk: &DataChunk) -> anyhow::Result<ChunkId> { + pub fn save(&mut self, meta: &ChunkMeta, chunk: &DataChunk) -> IndexedResult<ChunkId> { let id = ChunkId::new(); self.store.save(&id, meta, chunk)?; self.insert_meta(&id, meta)?; Ok(id) } - fn insert_meta(&mut self, id: &ChunkId, meta: &ChunkMeta) -> anyhow::Result<()> { + fn insert_meta(&mut self, id: &ChunkId, meta: &ChunkMeta) -> IndexedResult<()> { self.index.insert_meta(id.clone(), meta.clone())?; Ok(()) } - pub fn load(&self, id: &ChunkId) -> anyhow::Result<(DataChunk, ChunkMeta)> { + pub fn load(&self, id: &ChunkId) -> IndexedResult<(DataChunk, ChunkMeta)> { Ok((self.store.load(id)?, self.load_meta(id)?)) } - pub fn load_meta(&self, id: &ChunkId) -> anyhow::Result<ChunkMeta> { - self.index.get_meta(id) + pub fn load_meta(&self, id: &ChunkId) -> IndexedResult<ChunkMeta> { + Ok(self.index.get_meta(id)?) } - pub fn find_by_sha256(&self, sha256: &str) -> anyhow::Result<Vec<ChunkId>> { - self.index.find_by_sha256(sha256) + pub fn find_by_sha256(&self, sha256: &str) -> IndexedResult<Vec<ChunkId>> { + Ok(self.index.find_by_sha256(sha256)?) } - pub fn find_generations(&self) -> anyhow::Result<Vec<ChunkId>> { - self.index.find_generations() + pub fn find_generations(&self) -> IndexedResult<Vec<ChunkId>> { + Ok(self.index.find_generations()?) } - pub fn remove(&mut self, id: &ChunkId) -> anyhow::Result<()> { - self.index.remove_meta(id).unwrap(); + pub fn remove(&mut self, id: &ChunkId) -> IndexedResult<()> { + self.index.remove_meta(id)?; self.store.delete(id)?; Ok(()) } diff --git a/src/server.rs b/src/server.rs index 4d5880e..1b2dc29 100644 --- a/src/server.rs +++ b/src/server.rs @@ -50,7 +50,7 @@ impl SearchHits { self.map.insert(id.to_string(), meta); } - pub fn from_json(s: &str) -> anyhow::Result<Self> { + pub fn from_json(s: &str) -> Result<Self, serde_json::Error> { let map = serde_json::from_str(s)?; Ok(SearchHits { map }) } diff --git a/src/store.rs b/src/store.rs index e6cc71f..fca2c13 100644 --- a/src/store.rs +++ b/src/store.rs @@ -1,7 +1,6 @@ use crate::chunk::DataChunk; use crate::chunkid::ChunkId; use crate::chunkmeta::ChunkMeta; -use anyhow::Context; use std::path::{Path, PathBuf}; /// Store chunks, with metadata, persistently. @@ -13,6 +12,12 @@ pub struct Store { dir: PathBuf, } +/// An error from a `Store` operation. +pub type StoreError = std::io::Error; + +/// A result from an `Store` operation. +pub type StoreResult<T> = Result<T, StoreError>; + impl Store { /// Create a new Store to represent on-disk storage of chunks.x pub fn new(dir: &Path) -> Self { @@ -38,14 +43,11 @@ impl Store { } /// Save a chunk into a store. - pub fn save(&self, id: &ChunkId, meta: &ChunkMeta, chunk: &DataChunk) -> anyhow::Result<()> { + pub fn save(&self, id: &ChunkId, meta: &ChunkMeta, chunk: &DataChunk) -> StoreResult<()> { let (dir, metaname, dataname) = &self.filenames(id); if !dir.exists() { - let res = std::fs::create_dir_all(dir).into(); - if let Err(_) = res { - return res.with_context(|| format!("creating directory {}", dir.display())); - } + std::fs::create_dir_all(dir)?; } std::fs::write(&metaname, meta.to_json())?; @@ -54,7 +56,7 @@ impl Store { } /// Load a chunk from a store. - pub fn load(&self, id: &ChunkId) -> anyhow::Result<DataChunk> { + pub fn load(&self, id: &ChunkId) -> StoreResult<DataChunk> { let (_, _, dataname) = &self.filenames(id); let data = std::fs::read(&dataname)?; let data = DataChunk::new(data); @@ -62,7 +64,7 @@ impl Store { } /// Delete a chunk from a store. - pub fn delete(&self, id: &ChunkId) -> anyhow::Result<()> { + pub fn delete(&self, id: &ChunkId) -> StoreResult<()> { let (_, metaname, dataname) = &self.filenames(id); std::fs::remove_file(&metaname)?; std::fs::remove_file(&dataname)?; |