summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2021-02-03 09:11:49 +0200
committerLars Wirzenius <liw@liw.fi>2021-02-04 09:14:01 +0200
commita2adcb5a90c15b473a2fcf114555443fba8a20ce (patch)
tree7ec36f244daa105b0da774d6705ef736f9135f64 /src
parentbf08ea67ca035fc0e78364450599cefff7cd9bc6 (diff)
downloadobnam2-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.rs31
-rw-r--r--src/bin/obnam.rs2
-rw-r--r--src/chunk.rs17
-rw-r--r--src/chunker.rs14
-rw-r--r--src/client.rs88
-rw-r--r--src/cmd/backup.rs7
-rw-r--r--src/cmd/get_chunk.rs3
-rw-r--r--src/cmd/list.rs3
-rw-r--r--src/cmd/list_files.rs7
-rw-r--r--src/cmd/restore.rs46
-rw-r--r--src/cmd/show_gen.rs8
-rw-r--r--src/error.rs48
-rw-r--r--src/fsentry.rs17
-rw-r--r--src/fsiter.rs17
-rw-r--r--src/generation.rs83
-rw-r--r--src/genlist.rs12
-rw-r--r--src/index.rs55
-rw-r--r--src/indexedstore.rs43
-rw-r--r--src/server.rs2
-rw-r--r--src/store.rs18
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)?;