From 101f8980381f4d213ff4cd7e609f80c2d65cc927 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Wed, 23 Oct 2024 18:44:51 +0200 Subject: [PATCH 001/129] Removing anyhow --- Cargo.lock | 1 - crates/backend/Cargo.toml | 1 - crates/backend/src/choose.rs | 13 +- crates/backend/src/error.rs | 134 ---- crates/backend/src/lib.rs | 21 +- crates/backend/src/local.rs | 36 +- crates/backend/src/opendal.rs | 47 +- crates/backend/src/rclone.rs | 21 +- crates/backend/src/rest.rs | 25 +- crates/backend/src/util.rs | 4 +- crates/core/Cargo.toml | 2 +- crates/core/src/backend/cache.rs | 17 +- crates/core/src/backend/decrypt.rs | 31 +- crates/core/src/backend/dry_run.rs | 14 +- crates/core/src/backend/hotcold.rs | 15 +- crates/core/src/backend/ignore.rs | 2 +- crates/core/src/backend/warm_up.rs | 16 +- crates/core/src/error.rs | 1128 +++++++++------------------- 18 files changed, 514 insertions(+), 1014 deletions(-) delete mode 100644 crates/backend/src/error.rs diff --git a/Cargo.lock b/Cargo.lock index 0f5d8519..71745aa7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3270,7 +3270,6 @@ name = "rustic_backend" version = "0.4.1" dependencies = [ "aho-corasick", - "anyhow", "backoff", "bytes", "bytesize", diff --git a/crates/backend/Cargo.toml b/crates/backend/Cargo.toml index b35686b9..25b70d8b 100644 --- a/crates/backend/Cargo.toml +++ b/crates/backend/Cargo.toml @@ -44,7 +44,6 @@ rclone = ["rest", "dep:rand", "dep:semver"] rustic_core = { workspace = true } # errors -anyhow = "1.0.89" displaydoc = "0.2.5" thiserror = "1.0.64" diff --git a/crates/backend/src/choose.rs b/crates/backend/src/choose.rs index ab5007ca..51dadcea 100644 --- a/crates/backend/src/choose.rs +++ b/crates/backend/src/choose.rs @@ -1,11 +1,10 @@ //! This module contains [`BackendOptions`] and helpers to choose a backend from a given url. -use anyhow::{anyhow, Result}; use derive_setters::Setters; use std::{collections::HashMap, sync::Arc}; use strum_macros::{Display, EnumString}; #[allow(unused_imports)] -use rustic_core::{RepositoryBackends, WriteBackend}; +use rustic_core::{RepositoryBackends, RusticResult, WriteBackend}; use crate::{ error::BackendAccessErrorKind, @@ -75,12 +74,12 @@ impl BackendOptions { /// # Returns /// /// The backends for the repository. - pub fn to_backends(&self) -> Result { + pub fn to_backends(&self) -> RusticResult { let mut options = self.options.clone(); options.extend(self.options_cold.clone()); let be = self .get_backend(self.repository.as_ref(), options)? - .ok_or_else(|| anyhow!("No repository given."))?; + .ok_or_else(|| Err("No repository given.".to_string()).into())?; let mut options = self.options.clone(); options.extend(self.options_hot.clone()); let be_hot = self.get_backend(self.repo_hot.as_ref(), options)?; @@ -108,7 +107,7 @@ impl BackendOptions { &self, repo_string: Option<&String>, options: HashMap, - ) -> Result>> { + ) -> BackendResult>> { repo_string .map(|string| { let (be_type, location) = location_to_type_and_path(string)?; @@ -138,7 +137,7 @@ pub trait BackendChoice { &self, location: BackendLocation, options: Option>, - ) -> Result>; + ) -> RusticResult>; } /// The supported backend types. @@ -176,7 +175,7 @@ impl BackendChoice for SupportedBackend { &self, location: BackendLocation, options: Option>, - ) -> Result> { + ) -> RusticResult> { let options = options.unwrap_or_default(); Ok(match self { diff --git a/crates/backend/src/error.rs b/crates/backend/src/error.rs deleted file mode 100644 index f2aa3477..00000000 --- a/crates/backend/src/error.rs +++ /dev/null @@ -1,134 +0,0 @@ -#![allow(clippy::doc_markdown)] -use std::{num::TryFromIntError, process::ExitStatus, str::Utf8Error}; - -use displaydoc::Display; -use thiserror::Error; - -/// [`BackendAccessErrorKind`] describes the errors that can be returned by the various Backends -#[derive(Error, Debug, Display)] -#[non_exhaustive] -pub enum BackendAccessErrorKind { - /// backend {0:?} is not supported! - BackendNotSupported(String), - /// backend {0} cannot be loaded: {1:?} - BackendLoadError(String, anyhow::Error), - /// no suitable id found for {0} - NoSuitableIdFound(String), - /// id {0} is not unique - IdNotUnique(String), - /// {0:?} - #[error(transparent)] - FromIoError(#[from] std::io::Error), - /// {0:?} - #[error(transparent)] - FromTryIntError(#[from] TryFromIntError), - #[cfg(feature = "rest")] - /// backoff failed: {0:?} - BackoffError(#[from] backoff::Error), - /// parsing failed for url: `{0:?}` - UrlParsingFailed(#[from] url::ParseError), - /// creating data in backend failed - CreatingDataOnBackendFailed, - /// writing bytes to backend failed - WritingBytesToBackendFailed, - /// removing data from backend failed - RemovingDataFromBackendFailed, - /// failed to list files on Backend - ListingFilesOnBackendFailed, -} - -/// [`RcloneErrorKind`] describes the errors that can be returned by a backend provider -#[derive(Error, Debug, Display)] -#[non_exhaustive] -pub enum RcloneErrorKind { - /// 'rclone version' doesn't give any output - NoOutputForRcloneVersion, - /// cannot get stdout of rclone - NoStdOutForRclone, - /// rclone exited with `{0:?}` - RCloneExitWithBadStatus(ExitStatus), - /// url must start with http:\/\/! url: {0:?} - UrlNotStartingWithHttp(String), - /// StdIo Error: `{0:?}` - #[error(transparent)] - FromIoError(#[from] std::io::Error), - /// utf8 error: `{0:?}` - #[error(transparent)] - FromUtf8Error(#[from] Utf8Error), - /// error parsing verision number from `{0:?}` - FromParseVersion(String), - /// Using rclone without authentication! Upgrade to rclone >= 1.52.2 (current version: `{0}`)! - RCloneWithoutAuthentication(String), -} - -/// [`RestErrorKind`] describes the errors that can be returned while dealing with the REST API -#[derive(Error, Debug, Display)] -#[non_exhaustive] -pub enum RestErrorKind { - /// value `{0:?}` not supported for option retry! - NotSupportedForRetry(String), - /// parsing failed for url: `{0:?}` - UrlParsingFailed(#[from] url::ParseError), - #[cfg(feature = "rest")] - /// requesting resource failed: `{0:?}` - RequestingResourceFailed(#[from] reqwest::Error), - /// couldn't parse duration in humantime library: `{0:?}` - CouldNotParseDuration(#[from] humantime::DurationError), - #[cfg(feature = "rest")] - /// backoff failed: {0:?} - BackoffError(#[from] backoff::Error), - #[cfg(feature = "rest")] - /// Failed to build HTTP client: `{0:?}` - BuildingClientFailed(reqwest::Error), - /// joining URL failed on: {0:?} - JoiningUrlFailed(url::ParseError), -} - -/// [`LocalBackendErrorKind`] describes the errors that can be returned by an action on the filesystem in Backends -#[derive(Error, Debug, Display)] -#[non_exhaustive] -pub enum LocalBackendErrorKind { - /// directory creation failed: `{0:?}` - DirectoryCreationFailed(#[from] std::io::Error), - /// querying metadata failed: `{0:?}` - QueryingMetadataFailed(std::io::Error), - /// querying WalkDir metadata failed: `{0:?}` - QueryingWalkDirMetadataFailed(walkdir::Error), - /// executtion of command failed: `{0:?}` - CommandExecutionFailed(std::io::Error), - /// command was not successful for filename {file_name}, type {file_type}, id {id}: {status} - CommandNotSuccessful { - /// File name - file_name: String, - /// File type - file_type: String, - /// Item ID - id: String, - /// Exit status - status: ExitStatus, - }, - /// error building automaton `{0:?}` - FromAhoCorasick(#[from] aho_corasick::BuildError), - /// {0:?} - #[error(transparent)] - FromTryIntError(#[from] TryFromIntError), - /// {0:?} - #[error(transparent)] - FromWalkdirError(#[from] walkdir::Error), - /// removing file failed: `{0:?}` - FileRemovalFailed(std::io::Error), - /// opening file failed: `{0:?}` - OpeningFileFailed(std::io::Error), - /// setting file length failed: `{0:?}` - SettingFileLengthFailed(std::io::Error), - /// can't jump to position in file: `{0:?}` - CouldNotSeekToPositionInFile(std::io::Error), - /// couldn't write to buffer: `{0:?}` - CouldNotWriteToBuffer(std::io::Error), - /// reading file contents failed: `{0:?}` - ReadingContentsOfFileFailed(std::io::Error), - /// reading exact length of file contents failed: `{0:?}` - ReadingExactLengthOfFileFailed(std::io::Error), - /// failed to sync OS Metadata to disk: `{0:?}` - SyncingOfOsMetadataFailed(std::io::Error), -} diff --git a/crates/backend/src/lib.rs b/crates/backend/src/lib.rs index e24da48f..60a36440 100644 --- a/crates/backend/src/lib.rs +++ b/crates/backend/src/lib.rs @@ -53,27 +53,22 @@ This crate exposes a few features for controlling dependency usage: */ pub mod choose; -/// Error types for the backend. -pub mod error; /// Local backend for Rustic. pub mod local; +/// Utility functions for the backend. +pub mod util; + /// `OpenDAL` backend for Rustic. #[cfg(feature = "opendal")] pub mod opendal; + /// `Rclone` backend for Rustic. #[cfg(feature = "rclone")] pub mod rclone; + /// REST backend for Rustic. #[cfg(feature = "rest")] pub mod rest; -/// Utility functions for the backend. -pub mod util; - -// rustic_backend Public API -pub use crate::{ - choose::{BackendOptions, SupportedBackend}, - local::LocalBackend, -}; #[cfg(feature = "opendal")] pub use crate::opendal::OpenDALBackend; @@ -83,3 +78,9 @@ pub use crate::rclone::RcloneBackend; #[cfg(feature = "rest")] pub use crate::rest::RestBackend; + +// rustic_backend Public API +pub use crate::{ + choose::{BackendOptions, SupportedBackend}, + local::LocalBackend, +}; diff --git a/crates/backend/src/local.rs b/crates/backend/src/local.rs index df52ade5..62c00776 100644 --- a/crates/backend/src/local.rs +++ b/crates/backend/src/local.rs @@ -6,11 +6,14 @@ use std::{ }; use aho_corasick::AhoCorasick; -use anyhow::Result; use bytes::Bytes; use log::{debug, trace, warn}; use walkdir::WalkDir; +use rustic_core::{ + CommandInput, FileType, Id, ReadBackend, RusticResult, WriteBackend, ALL_FILE_TYPES, +}; + use rustic_core::{CommandInput, FileType, Id, ReadBackend, WriteBackend, ALL_FILE_TYPES}; use crate::error::LocalBackendErrorKind; @@ -41,7 +44,7 @@ impl LocalBackend { pub fn new( path: impl AsRef, options: impl IntoIterator, - ) -> Result { + ) -> RusticResult { let path = path.as_ref().into(); let mut post_create_command = None; let mut post_delete_command = None; @@ -58,6 +61,7 @@ impl LocalBackend { } } } + Ok(Self { path, post_create_command, @@ -113,7 +117,7 @@ impl LocalBackend { /// [`LocalBackendErrorKind::FromSplitError`]: LocalBackendErrorKind::FromSplitError /// [`LocalBackendErrorKind::CommandExecutionFailed`]: LocalBackendErrorKind::CommandExecutionFailed /// [`LocalBackendErrorKind::CommandNotSuccessful`]: LocalBackendErrorKind::CommandNotSuccessful - fn call_command(tpe: FileType, id: &Id, filename: &Path, command: &str) -> Result<()> { + fn call_command(tpe: FileType, id: &Id, filename: &Path, command: &str) -> RusticResult<()> { let id = id.to_hex(); let patterns = &["%file", "%type", "%id"]; let ac = AhoCorasick::new(patterns).map_err(LocalBackendErrorKind::FromAhoCorasick)?; @@ -157,7 +161,7 @@ impl ReadBackend for LocalBackend { /// # Notes /// /// If the file type is `FileType::Config`, this will return a list with a single default id. - fn list(&self, tpe: FileType) -> Result> { + fn list(&self, tpe: FileType) -> RusticResult> { trace!("listing tpe: {tpe:?}"); if tpe == FileType::Config { return Ok(if self.path.join("config").exists() { @@ -190,7 +194,7 @@ impl ReadBackend for LocalBackend { /// [`LocalBackendErrorKind::QueryingMetadataFailed`]: LocalBackendErrorKind::QueryingMetadataFailed /// [`LocalBackendErrorKind::FromTryIntError`]: LocalBackendErrorKind::FromTryIntError /// [`LocalBackendErrorKind::QueryingWalkDirMetadataFailed`]: LocalBackendErrorKind::QueryingWalkDirMetadataFailed - fn list_with_size(&self, tpe: FileType) -> Result> { + fn list_with_size(&self, tpe: FileType) -> RusticResult> { trace!("listing tpe: {tpe:?}"); let path = self.path.join(tpe.dirname()); @@ -213,7 +217,7 @@ impl ReadBackend for LocalBackend { .into_iter() .filter_map(walkdir::Result::ok) .filter(|e| e.file_type().is_file()) - .map(|e| -> Result<_> { + .map(|e| -> RusticResult<_> { Ok(( e.file_name().to_string_lossy().parse()?, e.metadata() @@ -223,7 +227,7 @@ impl ReadBackend for LocalBackend { .map_err(LocalBackendErrorKind::FromTryIntError)?, )) }) - .filter_map(Result::ok); + .filter_map(RusticResult::ok); Ok(walker.collect()) } @@ -240,7 +244,7 @@ impl ReadBackend for LocalBackend { /// * [`LocalBackendErrorKind::ReadingContentsOfFileFailed`] - If the file could not be read. /// /// [`LocalBackendErrorKind::ReadingContentsOfFileFailed`]: LocalBackendErrorKind::ReadingContentsOfFileFailed - fn read_full(&self, tpe: FileType, id: &Id) -> Result { + fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult { trace!("reading tpe: {tpe:?}, id: {id}"); Ok(fs::read(self.path(tpe, id)) .map_err(LocalBackendErrorKind::ReadingContentsOfFileFailed)? @@ -260,7 +264,7 @@ impl ReadBackend for LocalBackend { /// # Errors /// /// * [`LocalBackendErrorKind::OpeningFileFailed`] - If the file could not be opened. - /// * [`LocalBackendErrorKind::CouldNotSeekToPositionInFile`] - If the file could not be seeked to the given position. + /// * [`LocalBackendErrorKind::CouldNotSeekToPositionInFile`] - If the file could not be sought to the given position. /// * [`LocalBackendErrorKind::FromTryIntError`] - If the length of the file could not be converted to u32. /// * [`LocalBackendErrorKind::ReadingExactLengthOfFileFailed`] - If the length of the file could not be read. /// @@ -275,7 +279,7 @@ impl ReadBackend for LocalBackend { _cacheable: bool, offset: u32, length: u32, - ) -> Result { + ) -> RusticResult { trace!("reading tpe: {tpe:?}, id: {id}, offset: {offset}, length: {length}"); let mut file = File::open(self.path(tpe, id)).map_err(LocalBackendErrorKind::OpeningFileFailed)?; @@ -302,7 +306,7 @@ impl WriteBackend for LocalBackend { /// * [`LocalBackendErrorKind::DirectoryCreationFailed`] - If the directory could not be created. /// /// [`LocalBackendErrorKind::DirectoryCreationFailed`]: LocalBackendErrorKind::DirectoryCreationFailed - fn create(&self) -> Result<()> { + fn create(&self) -> RusticResult<()> { trace!("creating repo at {:?}", self.path); fs::create_dir_all(&self.path).map_err(LocalBackendErrorKind::DirectoryCreationFailed)?; @@ -339,7 +343,13 @@ impl WriteBackend for LocalBackend { /// [`LocalBackendErrorKind::SettingFileLengthFailed`]: LocalBackendErrorKind::SettingFileLengthFailed /// [`LocalBackendErrorKind::CouldNotWriteToBuffer`]: LocalBackendErrorKind::CouldNotWriteToBuffer /// [`LocalBackendErrorKind::SyncingOfOsMetadataFailed`]: LocalBackendErrorKind::SyncingOfOsMetadataFailed - fn write_bytes(&self, tpe: FileType, id: &Id, _cacheable: bool, buf: Bytes) -> Result<()> { + fn write_bytes( + &self, + tpe: FileType, + id: &Id, + _cacheable: bool, + buf: Bytes, + ) -> RusticResult<()> { trace!("writing tpe: {:?}, id: {}", &tpe, &id); let filename = self.path(tpe, id); let mut file = fs::OpenOptions::new() @@ -379,7 +389,7 @@ impl WriteBackend for LocalBackend { /// * [`LocalBackendErrorKind::FileRemovalFailed`] - If the file could not be removed. /// /// [`LocalBackendErrorKind::FileRemovalFailed`]: LocalBackendErrorKind::FileRemovalFailed - fn remove(&self, tpe: FileType, id: &Id, _cacheable: bool) -> Result<()> { + fn remove(&self, tpe: FileType, id: &Id, _cacheable: bool) -> RusticResult<()> { trace!("removing tpe: {:?}, id: {}", &tpe, &id); let filename = self.path(tpe, id); fs::remove_file(&filename).map_err(LocalBackendErrorKind::FileRemovalFailed)?; diff --git a/crates/backend/src/opendal.rs b/crates/backend/src/opendal.rs index 915b1ec9..4cd30087 100644 --- a/crates/backend/src/opendal.rs +++ b/crates/backend/src/opendal.rs @@ -1,7 +1,6 @@ /// `OpenDAL` backend for rustic. use std::{collections::HashMap, path::PathBuf, str::FromStr, sync::OnceLock}; -use anyhow::{anyhow, Error, Result}; use bytes::Bytes; use bytesize::ByteSize; use log::trace; @@ -12,7 +11,7 @@ use opendal::{ use rayon::prelude::{IntoParallelIterator, ParallelIterator}; use tokio::runtime::Runtime; -use rustic_core::{FileType, Id, ReadBackend, WriteBackend, ALL_FILE_TYPES}; +use rustic_core::{FileType, Id, ReadBackend, RusticResult, WriteBackend, ALL_FILE_TYPES}; mod constants { /// Default number of retries @@ -45,16 +44,20 @@ pub struct Throttle { } impl FromStr for Throttle { - type Err = Error; - fn from_str(s: &str) -> Result { + type Err = RusticError; + fn from_str(s: &str) -> RusticResult { let mut values = s .split(',') - .map(|s| ByteSize::from_str(s.trim()).map_err(|err| anyhow!("Error: {err}"))) - .map(|b| -> Result { Ok(b?.as_u64().try_into()?) }); + .map(|s| { + ByteSize::from_str(s.trim()).map_err(|err| Err(format!("Error: {err}")).into()) + }) + .map(|b| -> RusticResult { Ok(b?.as_u64().try_into()?) }); let bandwidth = values .next() - .ok_or_else(|| anyhow!("no bandwidth given"))??; - let burst = values.next().ok_or_else(|| anyhow!("no burst given"))??; + .ok_or_else(|| Err("no bandwidth given".to_string()).into())??; + let burst = values + .next() + .ok_or_else(|| Err("no burst given".to_string()).into())??; Ok(Self { bandwidth, burst }) } } @@ -74,7 +77,7 @@ impl OpenDALBackend { /// # Returns /// /// A new `OpenDAL` backend. - pub fn new(path: impl AsRef, options: HashMap) -> Result { + pub fn new(path: impl AsRef, options: HashMap) -> RusticResult { let max_retries = match options.get("retry").map(String::as_str) { Some("false" | "off") => 0, None | Some("default") => constants::DEFAULT_RETRY, @@ -154,7 +157,7 @@ impl ReadBackend for OpenDALBackend { /// # Notes /// /// If the file type is `FileType::Config`, this will return a list with a single default id. - fn list(&self, tpe: FileType) -> Result> { + fn list(&self, tpe: FileType) -> RusticResult> { trace!("listing tpe: {tpe:?}"); if tpe == FileType::Config { return Ok(if self.operator.is_exist("config")? { @@ -181,7 +184,7 @@ impl ReadBackend for OpenDALBackend { /// /// * `tpe` - The type of the files to list. /// - fn list_with_size(&self, tpe: FileType) -> Result> { + fn list_with_size(&self, tpe: FileType) -> RusticResult> { trace!("listing tpe: {tpe:?}"); if tpe == FileType::Config { return match self.operator.stat("config") { @@ -199,14 +202,14 @@ impl ReadBackend for OpenDALBackend { .call()? .into_iter() .filter(|e| e.metadata().is_file()) - .map(|e| -> Result<(Id, u32)> { + .map(|e| -> RusticResult<(Id, u32)> { Ok((e.name().parse()?, e.metadata().content_length().try_into()?)) }) - .filter_map(Result::ok) + .filter_map(RusticResult::ok) .collect()) } - fn read_full(&self, tpe: FileType, id: &Id) -> Result { + fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult { trace!("reading tpe: {tpe:?}, id: {id}"); Ok(self.operator.read(&self.path(tpe, id))?.to_bytes()) @@ -219,7 +222,7 @@ impl ReadBackend for OpenDALBackend { _cacheable: bool, offset: u32, length: u32, - ) -> Result { + ) -> RusticResult { trace!("reading tpe: {tpe:?}, id: {id}, offset: {offset}, length: {length}"); let range = u64::from(offset)..u64::from(offset + length); Ok(self @@ -233,7 +236,7 @@ impl ReadBackend for OpenDALBackend { impl WriteBackend for OpenDALBackend { /// Create a repository on the backend. - fn create(&self) -> Result<()> { + fn create(&self) -> RusticResult<()> { trace!("creating repo at {:?}", self.location()); for tpe in ALL_FILE_TYPES { @@ -262,7 +265,13 @@ impl WriteBackend for OpenDALBackend { /// * `id` - The id of the file. /// * `cacheable` - Whether the file is cacheable. /// * `buf` - The bytes to write. - fn write_bytes(&self, tpe: FileType, id: &Id, _cacheable: bool, buf: Bytes) -> Result<()> { + fn write_bytes( + &self, + tpe: FileType, + id: &Id, + _cacheable: bool, + buf: Bytes, + ) -> RusticResult<()> { trace!("writing tpe: {:?}, id: {}", &tpe, &id); let filename = self.path(tpe, id); self.operator.write(&filename, buf)?; @@ -276,7 +285,7 @@ impl WriteBackend for OpenDALBackend { /// * `tpe` - The type of the file. /// * `id` - The id of the file. /// * `cacheable` - Whether the file is cacheable. - fn remove(&self, tpe: FileType, id: &Id, _cacheable: bool) -> Result<()> { + fn remove(&self, tpe: FileType, id: &Id, _cacheable: bool) -> RusticResult<()> { trace!("removing tpe: {:?}, id: {}", &tpe, &id); let filename = self.path(tpe, id); self.operator.delete(&filename)?; @@ -314,7 +323,7 @@ mod tests { #[rstest] fn new_opendal_backend( #[files("tests/fixtures/opendal/*.toml")] test_case: PathBuf, - ) -> Result<()> { + ) -> RusticResult<()> { #[derive(Deserialize)] struct TestCase { path: String, diff --git a/crates/backend/src/rclone.rs b/crates/backend/src/rclone.rs index 84e72a86..22a4133f 100644 --- a/crates/backend/src/rclone.rs +++ b/crates/backend/src/rclone.rs @@ -5,7 +5,6 @@ use std::{ thread::JoinHandle, }; -use anyhow::Result; use bytes::Bytes; use constants::DEFAULT_COMMAND; use log::{debug, info}; @@ -15,6 +14,10 @@ use rand::{ }; use semver::{BuildMetadata, Prerelease, Version, VersionReq}; +use crate::rest::RestBackend; + +use rustic_core::{CommandInput, FileType, Id, ReadBackend, RusticResult, WriteBackend}; + use crate::{error::RcloneErrorKind, rest::RestBackend}; use rustic_core::{CommandInput, FileType, Id, ReadBackend, WriteBackend}; @@ -70,7 +73,7 @@ impl Drop for RcloneBackend { /// [`RcloneErrorKind::FromUtf8Error`]: RcloneErrorKind::FromUtf8Error /// [`RcloneErrorKind::NoOutputForRcloneVersion`]: RcloneErrorKind::NoOutputForRcloneVersion /// [`RcloneErrorKind::FromParseVersion`]: RcloneErrorKind::FromParseVersion -fn check_clone_version(rclone_version_output: &[u8]) -> Result<()> { +fn check_clone_version(rclone_version_output: &[u8]) -> RusticResult<()> { let rclone_version = std::str::from_utf8(rclone_version_output) .map_err(RcloneErrorKind::FromUtf8Error)? .lines() @@ -127,7 +130,7 @@ impl RcloneBackend { /// /// If the rclone command is not found. // TODO: This should be an error, not a panic. - pub fn new(url: impl AsRef, options: HashMap) -> Result { + pub fn new(url: impl AsRef, options: HashMap) -> RusticResult { let rclone_command = options.get("rclone-command"); let use_password = options .get("use-password") @@ -247,7 +250,7 @@ impl ReadBackend for RcloneBackend { /// * `tpe` - The type of the file. /// /// If the size could not be determined. - fn list_with_size(&self, tpe: FileType) -> Result> { + fn list_with_size(&self, tpe: FileType) -> RusticResult> { self.rest.list_with_size(tpe) } @@ -261,7 +264,7 @@ impl ReadBackend for RcloneBackend { /// # Returns /// /// The data read. - fn read_full(&self, tpe: FileType, id: &Id) -> Result { + fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult { self.rest.read_full(tpe, id) } @@ -285,14 +288,14 @@ impl ReadBackend for RcloneBackend { cacheable: bool, offset: u32, length: u32, - ) -> Result { + ) -> RusticResult { self.rest.read_partial(tpe, id, cacheable, offset, length) } } impl WriteBackend for RcloneBackend { /// Creates a new file. - fn create(&self) -> Result<()> { + fn create(&self) -> RusticResult<()> { self.rest.create() } @@ -304,7 +307,7 @@ impl WriteBackend for RcloneBackend { /// * `id` - The id of the file. /// * `cacheable` - Whether the data should be cached. /// * `buf` - The data to write. - fn write_bytes(&self, tpe: FileType, id: &Id, cacheable: bool, buf: Bytes) -> Result<()> { + fn write_bytes(&self, tpe: FileType, id: &Id, cacheable: bool, buf: Bytes) -> RusticResult<()> { self.rest.write_bytes(tpe, id, cacheable, buf) } @@ -315,7 +318,7 @@ impl WriteBackend for RcloneBackend { /// * `tpe` - The type of the file. /// * `id` - The id of the file. /// * `cacheable` - Whether the file is cacheable. - fn remove(&self, tpe: FileType, id: &Id, cacheable: bool) -> Result<()> { + fn remove(&self, tpe: FileType, id: &Id, cacheable: bool) -> RusticResult<()> { self.rest.remove(tpe, id, cacheable) } } diff --git a/crates/backend/src/rest.rs b/crates/backend/src/rest.rs index 3b0b5683..9bc4852b 100644 --- a/crates/backend/src/rest.rs +++ b/crates/backend/src/rest.rs @@ -1,7 +1,6 @@ use std::str::FromStr; use std::time::Duration; -use anyhow::Result; use backoff::{backoff::Backoff, ExponentialBackoff, ExponentialBackoffBuilder}; use bytes::Bytes; use log::{trace, warn}; @@ -138,7 +137,7 @@ impl RestBackend { pub fn new( url: impl AsRef, options: impl IntoIterator, - ) -> Result { + ) -> RusticResult { let url = url.as_ref(); let url = if url.ends_with('/') { Url::parse(url).map_err(RestErrorKind::UrlParsingFailed)? @@ -197,7 +196,7 @@ impl RestBackend { /// # Errors /// /// If the url could not be created. - fn url(&self, tpe: FileType, id: &Id) -> Result { + fn url(&self, tpe: FileType, id: &Id) -> Result { let id_path = if tpe == FileType::Config { "config".to_string() } else { @@ -245,7 +244,7 @@ impl ReadBackend for RestBackend { /// A vector of tuples containing the id and size of the files. /// /// [`RestErrorKind::JoiningUrlFailed`]: RestErrorKind::JoiningUrlFailed - fn list_with_size(&self, tpe: FileType) -> Result> { + fn list_with_size(&self, tpe: FileType) -> RusticResult> { // format which is delivered by the REST-service #[derive(Deserialize)] struct ListEntry { @@ -313,7 +312,7 @@ impl ReadBackend for RestBackend { /// * [`RestErrorKind::BackoffError`] - If the backoff failed. /// /// [`RestErrorKind::BackoffError`]: RestErrorKind::BackoffError - fn read_full(&self, tpe: FileType, id: &Id) -> Result { + fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult { trace!("reading tpe: {tpe:?}, id: {id}"); let url = self.url(tpe, id)?; Ok(backoff::retry_notify( @@ -353,7 +352,7 @@ impl ReadBackend for RestBackend { _cacheable: bool, offset: u32, length: u32, - ) -> Result { + ) -> RusticResult { trace!("reading tpe: {tpe:?}, id: {id}, offset: {offset}, length: {length}"); let offset2 = offset + length - 1; let header_value = format!("bytes={offset}-{offset2}"); @@ -383,7 +382,7 @@ impl WriteBackend for RestBackend { /// * [`RestErrorKind::BackoffError`] - If the backoff failed. /// /// [`RestErrorKind::BackoffError`]: RestErrorKind::BackoffError - fn create(&self) -> Result<()> { + fn create(&self) -> RusticResult<()> { let url = self .url .join("?create=true") @@ -413,13 +412,19 @@ impl WriteBackend for RestBackend { /// * [`RestErrorKind::BackoffError`] - If the backoff failed. /// /// [`RestErrorKind::BackoffError`]: RestErrorKind::BackoffError - fn write_bytes(&self, tpe: FileType, id: &Id, _cacheable: bool, buf: Bytes) -> Result<()> { + fn write_bytes( + &self, + tpe: FileType, + id: &Id, + _cacheable: bool, + buf: Bytes, + ) -> RusticResult<()> { trace!("writing tpe: {:?}, id: {}", &tpe, &id); let req_builder = self.client.post(self.url(tpe, id)?).body(buf); Ok(backoff::retry_notify( self.backoff.clone(), || { - // Note: try_clone() always gives Some(_) as the body is Bytes which is clonable + // Note: try_clone() always gives Some(_) as the body is Bytes which is cloneable _ = req_builder.try_clone().unwrap().send()?.check_error()?; Ok(()) }, @@ -441,7 +446,7 @@ impl WriteBackend for RestBackend { /// * [`RestErrorKind::BackoffError`] - If the backoff failed. /// /// [`RestErrorKind::BackoffError`]: RestErrorKind::BackoffError - fn remove(&self, tpe: FileType, id: &Id, _cacheable: bool) -> Result<()> { + fn remove(&self, tpe: FileType, id: &Id, _cacheable: bool) -> RusticResult<()> { trace!("removing tpe: {:?}, id: {}", &tpe, &id); let url = self.url(tpe, id)?; Ok(backoff::retry_notify( diff --git a/crates/backend/src/util.rs b/crates/backend/src/util.rs index ecae470b..fed4e194 100644 --- a/crates/backend/src/util.rs +++ b/crates/backend/src/util.rs @@ -1,5 +1,5 @@ use crate::SupportedBackend; -use anyhow::Result; +use rustic_core::{BackendErrorKind, BackendResult}; /// A backend location. This is a string that represents the location of the backend. #[derive(PartialEq, Eq, Debug)] @@ -45,7 +45,7 @@ impl std::fmt::Display for BackendLocation { /// If the url is a windows path, the type will be "local". pub fn location_to_type_and_path( raw_location: &str, -) -> Result<(SupportedBackend, BackendLocation)> { +) -> RusticResult<(SupportedBackend, BackendLocation)> { match raw_location.split_once(':') { #[cfg(windows)] Some((drive_letter, _)) if drive_letter.len() == 1 && !raw_location.contains('/') => Ok(( diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 374f5fde..52a3f12e 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -95,7 +95,6 @@ futures = { version = "0.3.30", optional = true } runtime-format = "0.1.3" # other dependencies -anyhow = { workspace = true } bytes = { workspace = true } bytesize = "1.3.0" chrono = { version = "0.4.38", default-features = false, features = ["clock", "serde"] } @@ -123,6 +122,7 @@ sha2 = "0.10.8" xattr = "1" [dev-dependencies] +anyhow = { workspace = true } expect-test = "1.5.0" flate2 = "1.0.34" globset = "0.4.15" diff --git a/crates/core/src/backend/cache.rs b/crates/core/src/backend/cache.rs index d00d9699..e1315bd1 100644 --- a/crates/core/src/backend/cache.rs +++ b/crates/core/src/backend/cache.rs @@ -6,7 +6,6 @@ use std::{ sync::Arc, }; -use anyhow::Result; use bytes::Bytes; use dirs::cache_dir; use log::{trace, warn}; @@ -14,7 +13,7 @@ use walkdir::WalkDir; use crate::{ backend::{FileType, ReadBackend, WriteBackend}, - error::{CacheBackendErrorKind, RusticResult}, + error::RusticResult, id::Id, repofile::configfile::RepositoryId, }; @@ -65,7 +64,7 @@ impl ReadBackend for CachedBackend { /// # Returns /// /// A vector of tuples containing the id and size of the files. - fn list_with_size(&self, tpe: FileType) -> Result> { + fn list_with_size(&self, tpe: FileType) -> RusticResult> { let list = self.be.list_with_size(tpe)?; if tpe.is_cacheable() { @@ -93,7 +92,7 @@ impl ReadBackend for CachedBackend { /// The data read. /// /// [`CacheBackendErrorKind::FromIoError`]: crate::error::CacheBackendErrorKind::FromIoError - fn read_full(&self, tpe: FileType, id: &Id) -> Result { + fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult { if tpe.is_cacheable() { match self.cache.read_full(tpe, id) { Ok(Some(data)) => return Ok(data), @@ -138,7 +137,7 @@ impl ReadBackend for CachedBackend { cacheable: bool, offset: u32, length: u32, - ) -> Result { + ) -> RusticResult { if cacheable || tpe.is_cacheable() { match self.cache.read_partial(tpe, id, offset, length) { Ok(Some(data)) => return Ok(data), @@ -164,14 +163,14 @@ impl ReadBackend for CachedBackend { self.be.needs_warm_up() } - fn warm_up(&self, tpe: FileType, id: &Id) -> Result<()> { + fn warm_up(&self, tpe: FileType, id: &Id) -> RusticResult<()> { self.be.warm_up(tpe, id) } } impl WriteBackend for CachedBackend { /// Creates the backend. - fn create(&self) -> Result<()> { + fn create(&self) -> RusticResult<()> { self.be.create() } @@ -185,7 +184,7 @@ impl WriteBackend for CachedBackend { /// * `id` - The id of the file. /// * `cacheable` - Whether the file is cacheable. /// * `buf` - The data to write. - fn write_bytes(&self, tpe: FileType, id: &Id, cacheable: bool, buf: Bytes) -> Result<()> { + fn write_bytes(&self, tpe: FileType, id: &Id, cacheable: bool, buf: Bytes) -> RusticResult<()> { if cacheable || tpe.is_cacheable() { if let Err(err) = self.cache.write_bytes(tpe, id, &buf) { warn!("Error in cache backend writing {tpe:?},{id}: {err}"); @@ -202,7 +201,7 @@ impl WriteBackend for CachedBackend { /// /// * `tpe` - The type of the file. /// * `id` - The id of the file. - fn remove(&self, tpe: FileType, id: &Id, cacheable: bool) -> Result<()> { + fn remove(&self, tpe: FileType, id: &Id, cacheable: bool) -> RusticResult<()> { if cacheable || tpe.is_cacheable() { if let Err(err) = self.cache.remove(tpe, id) { warn!("Error in cache backend removing {tpe:?},{id}: {err}"); diff --git a/crates/core/src/backend/decrypt.rs b/crates/core/src/backend/decrypt.rs index e90d6f86..ed114b5a 100644 --- a/crates/core/src/backend/decrypt.rs +++ b/crates/core/src/backend/decrypt.rs @@ -1,6 +1,5 @@ use std::{num::NonZeroU32, sync::Arc}; -use anyhow::Result; use bytes::Bytes; use crossbeam_channel::{unbounded, Receiver}; use rayon::prelude::*; @@ -8,21 +7,21 @@ use zstd::stream::{copy_encode, decode_all, encode_all}; pub use zstd::compression_level_range; -/// The maximum compression level allowed by zstd -#[must_use] -pub fn max_compression_level() -> i32 { - *compression_level_range().end() -} - use crate::{ backend::{FileType, ReadBackend, WriteBackend}, crypto::{hasher::hash, CryptoKey}, - error::{CryptBackendErrorKind, RusticErrorKind}, + error::RusticResult, id::Id, repofile::{RepoFile, RepoId}, Progress, RusticResult, }; +/// The maximum compression level allowed by zstd +#[must_use] +pub fn max_compression_level() -> i32 { + *compression_level_range().end() +} + /// A backend that can decrypt data. /// This is a trait that is implemented by all backends that can decrypt data. /// It is implemented for all backends that implement `DecryptWriteBackend` and `DecryptReadBackend`. @@ -387,7 +386,7 @@ impl DecryptBackend { } /// encrypt and potentially compress a repository file - fn encrypt_file(&self, data: &[u8]) -> RusticResult> { + fn encrypt_file(&self, data: &[u8]) -> CryptBackendResult> { let data_encrypted = match self.zstd { Some(level) => { let mut out = vec![2_u8]; @@ -542,15 +541,15 @@ impl ReadBackend for DecryptBackend { self.be.location() } - fn list(&self, tpe: FileType) -> Result> { + fn list(&self, tpe: FileType) -> RusticResult> { self.be.list(tpe) } - fn list_with_size(&self, tpe: FileType) -> Result> { + fn list_with_size(&self, tpe: FileType) -> RusticResult> { self.be.list_with_size(tpe) } - fn read_full(&self, tpe: FileType, id: &Id) -> Result { + fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult { self.be.read_full(tpe, id) } @@ -561,21 +560,21 @@ impl ReadBackend for DecryptBackend { cacheable: bool, offset: u32, length: u32, - ) -> Result { + ) -> RusticResult { self.be.read_partial(tpe, id, cacheable, offset, length) } } impl WriteBackend for DecryptBackend { - fn create(&self) -> Result<()> { + fn create(&self) -> RusticResult<()> { self.be.create() } - fn write_bytes(&self, tpe: FileType, id: &Id, cacheable: bool, buf: Bytes) -> Result<()> { + fn write_bytes(&self, tpe: FileType, id: &Id, cacheable: bool, buf: Bytes) -> RusticResult<()> { self.be.write_bytes(tpe, id, cacheable, buf) } - fn remove(&self, tpe: FileType, id: &Id, cacheable: bool) -> Result<()> { + fn remove(&self, tpe: FileType, id: &Id, cacheable: bool) -> RusticResult<()> { self.be.remove(tpe, id, cacheable) } } diff --git a/crates/core/src/backend/dry_run.rs b/crates/core/src/backend/dry_run.rs index e93c9023..9642b1c7 100644 --- a/crates/core/src/backend/dry_run.rs +++ b/crates/core/src/backend/dry_run.rs @@ -7,7 +7,7 @@ use crate::{ decrypt::{DecryptFullBackend, DecryptReadBackend, DecryptWriteBackend}, FileType, ReadBackend, WriteBackend, }, - error::{CryptBackendErrorKind, RusticErrorKind, RusticResult}, + error::RusticResult, id::Id, }; @@ -77,11 +77,11 @@ impl ReadBackend for DryRunBackend { self.be.location() } - fn list_with_size(&self, tpe: FileType) -> Result> { + fn list_with_size(&self, tpe: FileType) -> RusticResult> { self.be.list_with_size(tpe) } - fn read_full(&self, tpe: FileType, id: &Id) -> Result { + fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult { self.be.read_full(tpe, id) } @@ -92,7 +92,7 @@ impl ReadBackend for DryRunBackend { cacheable: bool, offset: u32, length: u32, - ) -> Result { + ) -> RusticResult { self.be.read_partial(tpe, id, cacheable, offset, length) } } @@ -133,7 +133,7 @@ impl DecryptWriteBackend for DryRunBackend { } impl WriteBackend for DryRunBackend { - fn create(&self) -> Result<()> { + fn create(&self) -> RusticResult<()> { if self.dry_run { Ok(()) } else { @@ -141,7 +141,7 @@ impl WriteBackend for DryRunBackend { } } - fn write_bytes(&self, tpe: FileType, id: &Id, cacheable: bool, buf: Bytes) -> Result<()> { + fn write_bytes(&self, tpe: FileType, id: &Id, cacheable: bool, buf: Bytes) -> RusticResult<()> { if self.dry_run { Ok(()) } else { @@ -149,7 +149,7 @@ impl WriteBackend for DryRunBackend { } } - fn remove(&self, tpe: FileType, id: &Id, cacheable: bool) -> Result<()> { + fn remove(&self, tpe: FileType, id: &Id, cacheable: bool) -> RusticResult<()> { if self.dry_run { Ok(()) } else { diff --git a/crates/core/src/backend/hotcold.rs b/crates/core/src/backend/hotcold.rs index 75a1f750..3885d85e 100644 --- a/crates/core/src/backend/hotcold.rs +++ b/crates/core/src/backend/hotcold.rs @@ -5,6 +5,7 @@ use bytes::Bytes; use crate::{ backend::{FileType, ReadBackend, WriteBackend}, + error::RusticResult, id::Id, }; @@ -45,11 +46,11 @@ impl ReadBackend for HotColdBackend { self.be.location() } - fn list_with_size(&self, tpe: FileType) -> Result> { + fn list_with_size(&self, tpe: FileType) -> RusticResult> { self.be.list_with_size(tpe) } - fn read_full(&self, tpe: FileType, id: &Id) -> Result { + fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult { self.be_hot.read_full(tpe, id) } @@ -60,7 +61,7 @@ impl ReadBackend for HotColdBackend { cacheable: bool, offset: u32, length: u32, - ) -> Result { + ) -> RusticResult { if cacheable || tpe != FileType::Pack { self.be_hot.read_partial(tpe, id, cacheable, offset, length) } else { @@ -72,25 +73,25 @@ impl ReadBackend for HotColdBackend { self.be.needs_warm_up() } - fn warm_up(&self, tpe: FileType, id: &Id) -> Result<()> { + fn warm_up(&self, tpe: FileType, id: &Id) -> RusticResult<()> { self.be.warm_up(tpe, id) } } impl WriteBackend for HotColdBackend { - fn create(&self) -> Result<()> { + fn create(&self) -> RusticResult<()> { self.be.create()?; self.be_hot.create() } - fn write_bytes(&self, tpe: FileType, id: &Id, cacheable: bool, buf: Bytes) -> Result<()> { + fn write_bytes(&self, tpe: FileType, id: &Id, cacheable: bool, buf: Bytes) -> RusticResult<()> { if tpe != FileType::Config && (cacheable || tpe != FileType::Pack) { self.be_hot.write_bytes(tpe, id, cacheable, buf.clone())?; } self.be.write_bytes(tpe, id, cacheable, buf) } - fn remove(&self, tpe: FileType, id: &Id, cacheable: bool) -> Result<()> { + fn remove(&self, tpe: FileType, id: &Id, cacheable: bool) -> RusticResult<()> { // First remove cold file self.be.remove(tpe, id, cacheable)?; if cacheable || tpe != FileType::Pack { diff --git a/crates/core/src/backend/ignore.rs b/crates/core/src/backend/ignore.rs index 4270851f..3f417902 100644 --- a/crates/core/src/backend/ignore.rs +++ b/crates/core/src/backend/ignore.rs @@ -28,7 +28,7 @@ use crate::{ node::{Metadata, Node, NodeType}, ReadSource, ReadSourceEntry, ReadSourceOpen, }, - error::{IgnoreErrorKind, RusticResult}, + error::RusticResult, }; /// A [`LocalSource`] is a source from local paths which is used to be read from (i.e. to backup it). diff --git a/crates/core/src/backend/warm_up.rs b/crates/core/src/backend/warm_up.rs index 87e9e009..3e25d49a 100644 --- a/crates/core/src/backend/warm_up.rs +++ b/crates/core/src/backend/warm_up.rs @@ -1,10 +1,10 @@ use std::sync::Arc; -use anyhow::Result; use bytes::Bytes; use crate::{ backend::{FileType, ReadBackend, WriteBackend}, + error::RusticResult, id::Id, }; @@ -31,11 +31,11 @@ impl ReadBackend for WarmUpAccessBackend { self.be.location() } - fn list_with_size(&self, tpe: FileType) -> Result> { + fn list_with_size(&self, tpe: FileType) -> RusticResult> { self.be.list_with_size(tpe) } - fn read_full(&self, tpe: FileType, id: &Id) -> Result { + fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult { self.be.read_full(tpe, id) } @@ -46,7 +46,7 @@ impl ReadBackend for WarmUpAccessBackend { cacheable: bool, offset: u32, length: u32, - ) -> Result { + ) -> RusticResult { self.be.read_partial(tpe, id, cacheable, offset, length) } @@ -54,7 +54,7 @@ impl ReadBackend for WarmUpAccessBackend { true } - fn warm_up(&self, tpe: FileType, id: &Id) -> Result<()> { + fn warm_up(&self, tpe: FileType, id: &Id) -> RusticResult<()> { // warm up files by accessing them - error is ignored as we expect this to error out! _ = self.be.read_partial(tpe, id, false, 0, 1); Ok(()) @@ -62,15 +62,15 @@ impl ReadBackend for WarmUpAccessBackend { } impl WriteBackend for WarmUpAccessBackend { - fn create(&self) -> Result<()> { + fn create(&self) -> RusticResult<()> { self.be.create() } - fn write_bytes(&self, tpe: FileType, id: &Id, cacheable: bool, buf: Bytes) -> Result<()> { + fn write_bytes(&self, tpe: FileType, id: &Id, cacheable: bool, buf: Bytes) -> RusticResult<()> { self.be.write_bytes(tpe, id, cacheable, buf) } - fn remove(&self, tpe: FileType, id: &Id, cacheable: bool) -> Result<()> { + fn remove(&self, tpe: FileType, id: &Id, cacheable: bool) -> RusticResult<()> { // First remove cold file self.be.remove(tpe, id, cacheable) } diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 844b14f1..a6992b88 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -5,806 +5,416 @@ // use std::error::Error as StdError; // use std::fmt; +use derive_setters::Setters; use std::{ - error::Error, - ffi::OsString, - num::{ParseFloatError, ParseIntError, TryFromIntError}, - ops::RangeInclusive, - path::{PathBuf, StripPrefixError}, - process::ExitStatus, - str::Utf8Error, + backtrace::Backtrace, + fmt::{self, Display}, }; -#[cfg(not(windows))] -use nix::errno::Errno; +use crate::error::immut_str::ImmutStr; -use aes256ctr_poly1305aes::aead; -use chrono::OutOfRangeError; -use displaydoc::Display; -use thiserror::Error; - -use crate::{ - backend::node::NodeType, - blob::{tree::TreeId, BlobId}, - repofile::{indexfile::IndexPack, packfile::PackId}, -}; +pub(crate) mod constants { + pub const DEFAULT_DOCS_URL: &str = "https://rustic.cli.rs/docs/errors/"; + pub const DEFAULT_ISSUE_URL: &str = "https://github.com/rustic-rs/rustic_core/issues/new"; +} /// Result type that is being returned from methods that can fail and thus have [`RusticError`]s. -pub type RusticResult = Result; +pub type RusticResult = Result; -// [`Error`] is public, but opaque and easy to keep compatible. -#[derive(Error, Debug)] -#[error(transparent)] +#[derive(thiserror::Error, Debug, Setters)] +#[setters(strip_option)] +#[non_exhaustive] /// Errors that can result from rustic. -pub struct RusticError(#[from] pub(crate) RusticErrorKind); +pub struct RusticError { + /// The kind of the error. + kind: ErrorKind, -// Accessors for anything we do want to expose publicly. -impl RusticError { - /// Expose the inner error kind. - /// - /// This is useful for matching on the error kind. - pub fn into_inner(self) -> RusticErrorKind { - self.0 - } + /// Chain to the cause of the error. + source: Option>, - /// Checks if the error is due to an incorrect password - pub fn is_incorrect_password(&self) -> bool { - matches!( - self.0, - RusticErrorKind::Repository(RepositoryErrorKind::IncorrectPassword) - ) - } + /// The error message with guidance. + guidance: ImmutStr, - /// Get the corresponding backend error, if error is caused by the backend. - /// - /// Returns `anyhow::Error`; you need to cast this to the real backend error type - pub fn backend_error(&self) -> Option<&anyhow::Error> { - if let RusticErrorKind::Backend(error) = &self.0 { - Some(error) - } else { - None - } - } -} + /// The context of the error. + context: Vec<(&'static str, String)>, -/// [`RusticErrorKind`] describes the errors that can happen while executing a high-level command. -/// -/// This is a non-exhaustive enum, so additional variants may be added in future. It is -/// recommended to match against the wildcard `_` instead of listing all possible variants, -/// to avoid problems when new variants are added. -#[non_exhaustive] -#[derive(Error, Debug)] -pub enum RusticErrorKind { - /// [`CommandErrorKind`] describes the errors that can happen while executing a high-level command - #[error(transparent)] - Command(#[from] CommandErrorKind), - - /// [`CryptoErrorKind`] describes the errors that can happen while dealing with Cryptographic functions - #[error(transparent)] - Crypto(#[from] CryptoErrorKind), - - /// [`PolynomialErrorKind`] describes the errors that can happen while dealing with Polynomials - #[error(transparent)] - Polynomial(#[from] PolynomialErrorKind), - - /// [`IdErrorKind`] describes the errors that can be returned by processing IDs - #[error(transparent)] - Id(#[from] IdErrorKind), - - /// [`RepositoryErrorKind`] describes the errors that can be returned by processing Repositories - #[error(transparent)] - Repository(#[from] RepositoryErrorKind), - - /// [`IndexErrorKind`] describes the errors that can be returned by processing Indizes - #[error(transparent)] - Index(#[from] IndexErrorKind), - - /// describes the errors that can be returned by the various Backends - #[error(transparent)] - Backend(#[from] anyhow::Error), - - /// [`BackendAccessErrorKind`] describes the errors that can be returned by accessing the various Backends - #[error(transparent)] - BackendAccess(#[from] BackendAccessErrorKind), - - /// [`ConfigFileErrorKind`] describes the errors that can be returned for `ConfigFile`s - #[error(transparent)] - ConfigFile(#[from] ConfigFileErrorKind), - - /// [`KeyFileErrorKind`] describes the errors that can be returned for `KeyFile`s - #[error(transparent)] - KeyFile(#[from] KeyFileErrorKind), - - /// [`PackFileErrorKind`] describes the errors that can be returned for `PackFile`s - #[error(transparent)] - PackFile(#[from] PackFileErrorKind), - - /// [`SnapshotFileErrorKind`] describes the errors that can be returned for `SnapshotFile`s - #[error(transparent)] - SnapshotFile(#[from] SnapshotFileErrorKind), - - /// [`PackerErrorKind`] describes the errors that can be returned for a Packer - #[error(transparent)] - Packer(#[from] PackerErrorKind), - - /// [`FileErrorKind`] describes the errors that can happen while dealing with files during restore/backups - #[error(transparent)] - File(#[from] FileErrorKind), - - /// [`TreeErrorKind`] describes the errors that can come up dealing with Trees - #[error(transparent)] - Tree(#[from] TreeErrorKind), - - /// [`CacheBackendErrorKind`] describes the errors that can be returned by a Caching action in Backends - #[error(transparent)] - CacheBackend(#[from] CacheBackendErrorKind), - - /// [`CryptBackendErrorKind`] describes the errors that can be returned by a Decryption action in Backends - #[error(transparent)] - CryptBackend(#[from] CryptBackendErrorKind), - - /// [`IgnoreErrorKind`] describes the errors that can be returned by a Ignore action in Backends - #[error(transparent)] - Ignore(#[from] IgnoreErrorKind), - - /// [`LocalDestinationErrorKind`] describes the errors that can be returned by an action on the local filesystem as Destination - #[error(transparent)] - LocalDestination(#[from] LocalDestinationErrorKind), - - /// [`NodeErrorKind`] describes the errors that can be returned by an action utilizing a node in Backends - #[error(transparent)] - Node(#[from] NodeErrorKind), - - /// [`StdInErrorKind`] describes the errors that can be returned while dealing IO from CLI - #[error(transparent)] - StdIn(#[from] StdInErrorKind), - - /// [`ArchiverErrorKind`] describes the errors that can be returned from the archiver - #[error(transparent)] - ArchiverError(#[from] ArchiverErrorKind), - - /// [`VfsErrorKind`] describes the errors that can be returned from the Virtual File System - #[error(transparent)] - VfsError(#[from] VfsErrorKind), - - /// [`std::io::Error`] - #[error(transparent)] - StdIo(#[from] std::io::Error), -} + /// The URL of the documentation for the error. + docs_url: Option, -/// [`CommandErrorKind`] describes the errors that can happen while executing a high-level command -#[derive(Error, Debug, Display)] -pub enum CommandErrorKind { - /// path is no dir: `{0}` - PathIsNoDir(String), - /// used blobs are missing: blob `{0}` doesn't existing - BlobsMissing(BlobId), - /// used pack `{0}`: size does not match! Expected size: `{1}`, real size: `{2}` - PackSizeNotMatching(PackId, u32, u32), - /// used pack `{0}` does not exist! - PackNotExisting(PackId), - /// pack `{0}` got no decision what to do - NoDecision(PackId), - /// [`std::num::ParseFloatError`] - #[error(transparent)] - FromParseFloatError(#[from] ParseFloatError), - /// [`std::num::ParseIntError`] - #[error(transparent)] - FromParseIntError(#[from] ParseIntError), - /// Bytesize parser failed: `{0}` - FromByteSizeParser(String), - /// --repack-uncompressed makes no sense for v1 repo! - RepackUncompressedRepoV1, - /// datetime out of range: `{0}` - FromOutOfRangeError(#[from] OutOfRangeError), - /// node type `{0:?}` not supported by dump - DumpNotSupported(NodeType), - /// [`serde_json::Error`] - #[error(transparent)] - FromJsonError(#[from] serde_json::Error), - /// version `{0}` is not supported. Allowed values: {1:?} - VersionNotSupported(u32, RangeInclusive), - /// cannot downgrade version from `{0}` to `{1}` - CannotDowngrade(u32, u32), - /// compression level `{0}` is not supported for repo v1 - NoCompressionV1Repo(i32), - /// compression level `{0}` is not supported. Allowed values: `{1:?}` - CompressionLevelNotSupported(i32, RangeInclusive), - /// Size is too large: `{0}` - SizeTooLarge(bytesize::ByteSize), - /// min_packsize_tolerate_percent must be <= 100 - MinPackSizeTolerateWrong, - /// max_packsize_tolerate_percent must be >= 100 or 0" - MaxPackSizeTolerateWrong, - /// error creating `{0:?}`: `{1:?}` - ErrorCreating(PathBuf, Box), - /// error collecting information for `{0:?}`: `{1:?}` - ErrorCollecting(PathBuf, Box), - /// error setting length for `{0:?}`: `{1:?}` - ErrorSettingLength(PathBuf, Box), - /// [`rayon::ThreadPoolBuildError`] - #[error(transparent)] - FromRayonError(#[from] rayon::ThreadPoolBuildError), - /// Conversion from integer failed: `{0:?}` - ConversionFromIntFailed(TryFromIntError), - /// Not allowed on an append-only repository: `{0}` - NotAllowedWithAppendOnly(String), - /// Specify one of the keep-* options for forget! Please use keep-none to keep no snapshot. - NoKeepOption, - /// [`shell_words::ParseError`] - #[error(transparent)] - FromParseError(#[from] shell_words::ParseError), -} + /// Error code. + code: Option, -/// [`CryptoErrorKind`] describes the errors that can happen while dealing with Cryptographic functions -#[derive(Error, Debug, Display, Copy, Clone)] -pub enum CryptoErrorKind { - /// data decryption failed: `{0:?}` - DataDecryptionFailed(aead::Error), - /// data encryption failed: `{0:?}` - DataEncryptionFailed(aead::Error), - /// crypto key too short - CryptoKeyTooShort, -} + /// The URL of the issue tracker for opening a new issue. + new_issue_url: Option, -/// [`PolynomialErrorKind`] describes the errors that can happen while dealing with Polynomials -#[derive(Error, Debug, Display, Copy, Clone)] -pub enum PolynomialErrorKind { - /// no suitable polynomial found - NoSuitablePolynomialFound, -} + /// The URL of an already existing issue that is related to this error. + existing_issue_url: Option, -/// [`FileErrorKind`] describes the errors that can happen while dealing with files during restore/backups -#[derive(Error, Debug, Display)] -pub enum FileErrorKind { - /// transposing an Option of a Result into a Result of an Option failed: `{0:?}` - TransposingOptionResultFailed(std::io::Error), - /// conversion from `u64` to `usize` failed: `{0:?}` - ConversionFromU64ToUsizeFailed(TryFromIntError), -} + /// Severity of the error. + severity: Option, -/// [`IdErrorKind`] describes the errors that can be returned by processing IDs -#[derive(Error, Debug, Display, Copy, Clone)] -pub enum IdErrorKind { - /// Hex decoding error: `{0:?}` - HexError(hex::FromHexError), -} + /// The status of the error. + status: Option, -/// [`RepositoryErrorKind`] describes the errors that can be returned by processing Repositories -#[derive(Error, Debug, Display)] -pub enum RepositoryErrorKind { - /// No repository given. Please use the --repository option. - NoRepositoryGiven, - /// No password given. Please use one of the --password-* options. - NoPasswordGiven, - /// warm-up command must contain %id! - NoIDSpecified, - /// error opening password file `{0:?}` - OpeningPasswordFileFailed(std::io::Error), - /// No repository config file found. Is there a repo at `{0}`? - NoRepositoryConfigFound(String), - /// More than one repository config file at `{0}`. Aborting. - MoreThanOneRepositoryConfig(String), - /// keys from repo and repo-hot do not match for `{0}`. Aborting. - KeysDontMatchForRepositories(String), - /// repository is a hot repository!\nPlease use as --repo-hot in combination with the normal repo. Aborting. - HotRepositoryFlagMissing, - /// repo-hot is not a hot repository! Aborting. - IsNotHotRepository, - /// incorrect password! - IncorrectPassword, - /// error running the password command - PasswordCommandExecutionFailed, - /// error reading password from command - ReadingPasswordFromCommandFailed, - /// running command `{0}`:`{1}` was not successful: `{2}` - CommandExecutionFailed(String, String, std::io::Error), - /// running command {0}:{1} returned status: `{2}` - CommandErrorStatus(String, String, ExitStatus), - /// error listing the repo config file - ListingRepositoryConfigFileFailed, - /// error listing the repo keys - ListingRepositoryKeysFailed, - /// error listing the hot repo keys - ListingHotRepositoryKeysFailed, - /// error accessing config file - AccessToConfigFileFailed, - /// Thread pool build error: `{0:?}` - FromThreadPoolbilderError(rayon::ThreadPoolBuildError), - /// reading Password failed: `{0:?}` - ReadingPasswordFromReaderFailed(std::io::Error), - /// reading Password from prompt failed: `{0:?}` - ReadingPasswordFromPromptFailed(std::io::Error), - /// Config file already exists. Aborting. - ConfigFileExists, - /// did not find id `{0}` in index - IdNotFound(BlobId), - /// no suitable backend type found - NoBackendTypeGiven, + /// Backtrace of the error. + backtrace: Option, } -/// [`IndexErrorKind`] describes the errors that can be returned by processing Indizes -#[derive(Error, Debug, Display, Clone, Copy)] -pub enum IndexErrorKind { - /// blob not found in index - BlobInIndexNotFound, - /// failed to get a blob from the backend - GettingBlobIndexEntryFromBackendFailed, - /// saving IndexFile failed - SavingIndexFileFailed, -} +impl Display for RusticError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "An error occurred in `rustic_core`: {}", self.kind)?; + + write!(f, "\nMessage: {}", self.guidance)?; + + if !self.context.is_empty() { + write!(f, "\n\n Context:\n")?; + write!( + f, + "{}", + self.context + .iter() + .map(|(k, v)| format!("{k}: {v}")) + .collect::>() + .join(", ") + )?; + } -/// [`BackendAccessErrorKind`] describes the errors that can be returned by the various Backends -#[derive(Error, Debug, Display)] -pub enum BackendAccessErrorKind { - /// backend `{0:?}` is not supported! - BackendNotSupported(String), - /// backend `{0}` cannot be loaded: {1:?} - BackendLoadError(String, anyhow::Error), - /// no suitable id found for `{0}` - NoSuitableIdFound(String), - /// id `{0}` is not unique - IdNotUnique(String), - /// [`std::io::Error`] - #[error(transparent)] - FromIoError(#[from] std::io::Error), - /// [`std::num::TryFromIntError`] - #[error(transparent)] - FromTryIntError(#[from] TryFromIntError), - /// [`LocalDestinationErrorKind`] - #[error(transparent)] - FromLocalError(#[from] LocalDestinationErrorKind), - /// [`IdErrorKind`] - #[error(transparent)] - FromIdError(#[from] IdErrorKind), - /// [`IndexErrorKind`] - #[error(transparent)] - FromIgnoreError(#[from] IgnoreErrorKind), - /// [`CryptBackendErrorKind`] - #[error(transparent)] - FromBackendDecryptionError(#[from] CryptBackendErrorKind), - /// [`ignore::Error`] - #[error(transparent)] - GenericError(#[from] ignore::Error), - /// creating data in backend failed - CreatingDataOnBackendFailed, - /// writing bytes to backend failed - WritingBytesToBackendFailed, - /// removing data from backend failed - RemovingDataFromBackendFailed, - /// failed to list files on Backend - ListingFilesOnBackendFailed, - /// Path is not allowed: `{0:?}` - PathNotAllowed(PathBuf), -} + if let Some(cause) = &self.source { + write!(f, "\n\nCaused by: {cause}")?; + } -/// [`ConfigFileErrorKind`] describes the errors that can be returned for `ConfigFile`s -#[derive(Error, Debug, Display)] -pub enum ConfigFileErrorKind { - /// config version not supported! - ConfigVersionNotSupported, - /// Parsing Polynomial in config failed: `{0:?}` - ParsingFailedForPolynomial(#[from] ParseIntError), -} + if let Some(severity) = &self.severity { + write!(f, "\n\nSeverity: {severity:?}", severity = severity)?; + } -/// [`KeyFileErrorKind`] describes the errors that can be returned for `KeyFile`s -#[derive(Error, Debug, Display)] -pub enum KeyFileErrorKind { - /// no suitable key found! - NoSuitableKeyFound, - /// listing KeyFiles failed - ListingKeyFilesFailed, - /// couldn't get KeyFile from backend - CouldNotGetKeyFileFromBackend, - /// serde_json couldn't deserialize the data: `{0:?}` - DeserializingFromSliceFailed(serde_json::Error), - /// couldn't encrypt data: `{0:?}` - CouldNotEncryptData(#[from] CryptoErrorKind), - /// serde_json couldn't serialize the data into a JSON byte vector: `{0:?}` - CouldNotSerializeAsJsonByteVector(serde_json::Error), - /// conversion from `u32` to `u8` failed: `{0:?}` - ConversionFromU32ToU8Failed(TryFromIntError), - /// output length is invalid: `{0:?}` - OutputLengthInvalid(scrypt::errors::InvalidOutputLen), - /// invalid scrypt parameters: `{0:?}` - InvalidSCryptParameters(scrypt::errors::InvalidParams), -} + if let Some(status) = &self.status { + write!(f, "\n\nStatus: {status:?}")?; + } -/// [`PackFileErrorKind`] describes the errors that can be returned for `PackFile`s -#[derive(Error, Debug, Display)] -pub enum PackFileErrorKind { - /// Failed reading binary representation of the pack header: `{0:?}` - ReadingBinaryRepresentationFailed(binrw::Error), - /// Failed writing binary representation of the pack header: `{0:?}` - WritingBinaryRepresentationFailed(binrw::Error), - /// Read header length is too large! Length: `{size_real}`, file size: `{pack_size}` - HeaderLengthTooLarge { size_real: u32, pack_size: u32 }, - /// Read header length doesn't match header contents! Length: `{size_real}`, computed: `{size_computed}` - HeaderLengthDoesNotMatchHeaderContents { size_real: u32, size_computed: u32 }, - /// pack size computed from header doesn't match real pack isch! Computed: `{size_computed}`, real: `{size_real}` - HeaderPackSizeComputedDoesNotMatchRealPackFile { size_real: u32, size_computed: u32 }, - /// partially reading the pack header from packfile failed: `{0:?}` - ListingKeyFilesFailed(#[from] BackendAccessErrorKind), - /// decrypting from binary failed - BinaryDecryptionFailed, - /// Partial read of PackFile failed - PartialReadOfPackfileFailed, - /// writing Bytes failed - WritingBytesFailed, - /// [`CryptBackendErrorKind`] - #[error(transparent)] - PackDecryptionFailed(#[from] CryptBackendErrorKind), -} + if let Some(code) = &self.code { + let default_docs_url = ImmutStr::from(constants::DEFAULT_DOCS_URL); + let docs_url = self.docs_url.as_ref().unwrap_or(&default_docs_url); -/// [`SnapshotFileErrorKind`] describes the errors that can be returned for `SnapshotFile`s -#[derive(Error, Debug, Display)] -pub enum SnapshotFileErrorKind { - /// non-unicode hostname `{0:?}` - NonUnicodeHostname(OsString), - /// non-unicode path `{0:?}` - NonUnicodePath(PathBuf), - /// no snapshots found - NoSnapshotsFound, - /// value `{0:?}` not allowed - ValueNotAllowed(String), - /// datetime out of range: `{0:?}` - OutOfRange(#[from] OutOfRangeError), - /// reading the description file failed: `{0:?}` - ReadingDescriptionFailed(#[from] std::io::Error), - /// getting the SnapshotFile from the backend failed - GettingSnapshotFileFailed, - /// getting the SnapshotFile by ID failed - GettingSnapshotFileByIdFailed, - /// unpacking SnapshotFile result failed - UnpackingSnapshotFileResultFailed, - /// collecting IDs failed: `{0:?}` - FindingIdsFailed(Vec), - /// removing dots from paths failed: `{0:?}` - RemovingDotsFromPathFailed(std::io::Error), - /// canonicalizing path failed: `{0:?}` - CanonicalizingPathFailed(std::io::Error), -} + write!(f, "\n\nFor more information, see: {docs_url}/{code}")?; + } -/// [`PackerErrorKind`] describes the errors that can be returned for a Packer -#[derive(Error, Debug, Display)] -pub enum PackerErrorKind { - /// error returned by cryptographic libraries: `{0:?}` - CryptoError(#[from] CryptoErrorKind), - /// could not compress due to unsupported config version: `{0:?}` - ConfigVersionNotSupported(#[from] ConfigFileErrorKind), - /// compressing data failed: `{0:?}` - CompressingDataFailed(#[from] std::io::Error), - /// getting total size failed - GettingTotalSizeFailed, - /// [`crossbeam_channel::SendError`] - #[error(transparent)] - SendingCrossbeamMessageFailed( - #[from] crossbeam_channel::SendError<(bytes::Bytes, BlobId, Option)>, - ), - /// [`crossbeam_channel::SendError`] - #[error(transparent)] - SendingCrossbeamMessageFailedForIndexPack( - #[from] crossbeam_channel::SendError<(bytes::Bytes, IndexPack)>, - ), - /// couldn't create binary representation for pack header: `{0:?}` - CouldNotCreateBinaryRepresentationForHeader(#[from] PackFileErrorKind), - /// failed to write bytes in backend: `{0:?}` - WritingBytesFailedInBackend(#[from] BackendAccessErrorKind), - /// failed to write bytes for PackFile: `{0:?}` - WritingBytesFailedForPackFile(PackFileErrorKind), - /// failed to read partially encrypted data: `{0:?}` - ReadingPartiallyEncryptedDataFailed(#[from] CryptBackendErrorKind), - /// failed to partially read data: `{0:?}` - PartiallyReadingDataFailed(PackFileErrorKind), - /// failed to add index pack: `{0:?}` - AddingIndexPackFailed(#[from] IndexErrorKind), - /// conversion for integer failed: `{0:?}` - IntConversionFailed(#[from] TryFromIntError), -} + if let Some(existing_issue_url) = &self.existing_issue_url { + write!(f, "\n\nThis might be a related issue, please check it for a possible workaround and/or further guidance: {existing_issue_url}")?; + } -/// [`TreeErrorKind`] describes the errors that can come up dealing with Trees -#[derive(Error, Debug, Display)] -pub enum TreeErrorKind { - /// blob `{0}` not found in index - BlobIdNotFound(TreeId), - /// `{0:?}` is not a directory - NotADirectory(OsString), - /// Path `{0:?}` not found - PathNotFound(OsString), - /// path should not contain current or parent dir - ContainsCurrentOrParentDirectory, - /// serde_json couldn't serialize the tree: `{0:?}` - SerializingTreeFailed(#[from] serde_json::Error), - /// serde_json couldn't deserialize tree from bytes of JSON text: `{0:?}` - DeserializingTreeFailed(serde_json::Error), - /// reading blob data failed `{0:?}` - ReadingBlobDataFailed(#[from] IndexErrorKind), - /// slice is not UTF-8: `{0:?}` - PathIsNotUtf8Conform(#[from] Utf8Error), - /// error in building nodestreamer: `{0:?}` - BuildingNodeStreamerFailed(#[from] ignore::Error), - /// failed to read file string from glob file: `{0:?}` - ReadingFileStringFromGlobsFailed(#[from] std::io::Error), - /// [`crossbeam_channel::SendError`] - #[error(transparent)] - SendingCrossbeamMessageFailed(#[from] crossbeam_channel::SendError<(PathBuf, TreeId, usize)>), - /// [`crossbeam_channel::RecvError`] - #[error(transparent)] - ReceivingCrossbreamMessageFailed(#[from] crossbeam_channel::RecvError), -} + let default_issue_url = ImmutStr::from(constants::DEFAULT_ISSUE_URL); + let new_issue_url = self.new_issue_url.as_ref().unwrap_or(&default_issue_url); -/// [`CacheBackendErrorKind`] describes the errors that can be returned by a Caching action in Backends -#[derive(Error, Debug, Display)] -pub enum CacheBackendErrorKind { - /// no cache dir - NoCacheDirectory, - /// [`std::io::Error`] - #[error(transparent)] - FromIoError(#[from] std::io::Error), - /// setting option on CacheBackend failed - SettingOptionOnCacheBackendFailed, - /// listing with size on CacheBackend failed - ListingWithSizeOnCacheBackendFailed, - /// fully reading from CacheBackend failed - FullyReadingFromCacheBackendFailed, - /// partially reading from CacheBackend failed - PartiallyReadingFromBackendDataFailed, - /// creating data on CacheBackend failed - CreatingDataOnCacheBackendFailed, - /// writing bytes on CacheBackend failed - WritingBytesOnCacheBackendFailed, - /// removing data on CacheBackend failed - RemovingDataOnCacheBackendFailed, -} + write!( + f, + "\n\nIf you think this is an undiscovered bug, please open an issue at: {new_issue_url}" + )?; -/// [`CryptBackendErrorKind`] describes the errors that can be returned by a Decryption action in Backends -#[derive(Error, Debug, Display)] -pub enum CryptBackendErrorKind { - /// decryption not supported for backend - DecryptionNotSupportedForBackend, - /// length of uncompressed data does not match! - LengthOfUncompressedDataDoesNotMatch, - /// failed to read encrypted data during full read - DecryptionInFullReadFailed, - /// failed to read encrypted data during partial read - DecryptionInPartialReadFailed, - /// decrypting from backend failed - DecryptingFromBackendFailed, - /// deserializing from bytes of JSON Text failed: `{0:?}` - DeserializingFromBytesOfJsonTextFailed(serde_json::Error), - /// failed to write data in crypt backend - WritingDataInCryptBackendFailed, - /// failed to list Ids - ListingIdsInDecryptionBackendFailed, - /// [`CryptoErrorKind`] - #[error(transparent)] - FromKey(#[from] CryptoErrorKind), - /// [`std::io::Error`] - #[error(transparent)] - FromIo(#[from] std::io::Error), - /// [`serde_json::Error`] - #[error(transparent)] - FromJson(#[from] serde_json::Error), - /// writing full hash failed in CryptBackend - WritingFullHashFailed, - /// decoding Zstd compressed data failed: `{0:?}` - DecodingZstdCompressedDataFailed(std::io::Error), - /// Serializing to JSON byte vector failed: `{0:?}` - SerializingToJsonByteVectorFailed(serde_json::Error), - /// encrypting data failed - EncryptingDataFailed, - /// Compressing and appending data failed: `{0:?}` - CopyEncodingDataFailed(std::io::Error), - /// conversion for integer failed: `{0:?}` - IntConversionFailed(#[from] TryFromIntError), - /// Extra verification failed: After decrypting and decompressing the data changed! - ExtraVerificationFailed, -} + if let Some(backtrace) = &self.backtrace { + write!(f, "\n\nBacktrace:\n{:?}", backtrace)?; + } -/// [`IgnoreErrorKind`] describes the errors that can be returned by a Ignore action in Backends -#[derive(Error, Debug, Display)] -pub enum IgnoreErrorKind { - /// generic Ignore error: `{0:?}` - GenericError(#[from] ignore::Error), - /// Error reading glob file `{file:?}`: `{source:?}` - ErrorGlob { - file: PathBuf, - source: std::io::Error, - }, - /// Unable to open file `{file:?}`: `{source:?}` - UnableToOpenFile { - file: PathBuf, - source: std::io::Error, - }, - /// Error getting xattrs for `{path:?}`: `{source:?}` - ErrorXattr { - path: PathBuf, - source: std::io::Error, - }, - /// Error reading link target for `{path:?}`: `{source:?}` - ErrorLink { - path: PathBuf, - source: std::io::Error, - }, - /// [`std::num::TryFromIntError`] - #[error(transparent)] - FromTryFromIntError(#[from] TryFromIntError), + Ok(()) + } } -/// [`LocalDestinationErrorKind`] describes the errors that can be returned by an action on the filesystem in Backends -#[derive(Error, Debug, Display)] -pub enum LocalDestinationErrorKind { - /// directory creation failed: `{0:?}` - DirectoryCreationFailed(#[from] std::io::Error), - /// file `{0:?}` should have a parent - FileDoesNotHaveParent(PathBuf), - /// [`std::num::TryFromIntError`] - #[error(transparent)] - FromTryIntError(#[from] TryFromIntError), - /// [`IdErrorKind`] - #[error(transparent)] - FromIdError(#[from] IdErrorKind), - /// [`walkdir::Error`] - #[error(transparent)] - FromWalkdirError(#[from] walkdir::Error), - /// [`Errno`] - #[error(transparent)] - #[cfg(not(windows))] - FromErrnoError(#[from] Errno), - /// listing xattrs on `{path:?}`: `{source:?}` - #[cfg(not(any(windows, target_os = "openbsd")))] - ListingXattrsFailed { - path: PathBuf, - source: std::io::Error, - }, - /// setting xattr `{name}` on `{filename:?}` with `{source:?}` - #[cfg(not(any(windows, target_os = "openbsd")))] - SettingXattrFailed { - name: String, - filename: PathBuf, - source: std::io::Error, - }, - /// getting xattr `{name}` on `{filename:?}` with `{source:?}` - #[cfg(not(any(windows, target_os = "openbsd")))] - GettingXattrFailed { - name: String, - filename: PathBuf, - source: std::io::Error, - }, - /// removing directories failed: `{0:?}` - DirectoryRemovalFailed(std::io::Error), - /// removing file failed: `{0:?}` - FileRemovalFailed(std::io::Error), - /// setting time metadata failed: `{0:?}` - SettingTimeMetadataFailed(std::io::Error), - /// opening file failed: `{0:?}` - OpeningFileFailed(std::io::Error), - /// setting file length failed: `{0:?}` - SettingFileLengthFailed(std::io::Error), - /// can't jump to position in file: `{0:?}` - CouldNotSeekToPositionInFile(std::io::Error), - /// couldn't write to buffer: `{0:?}` - CouldNotWriteToBuffer(std::io::Error), - /// reading exact length of file contents failed: `{0:?}` - ReadingExactLengthOfFileFailed(std::io::Error), - /// setting file permissions failed: `{0:?}` - #[cfg(not(windows))] - SettingFilePermissionsFailed(std::io::Error), - /// failed to symlink target `{linktarget:?}` from `{filename:?}` with `{source:?}` - #[cfg(not(windows))] - SymlinkingFailed { - linktarget: PathBuf, - filename: PathBuf, - source: std::io::Error, - }, +// Accessors for anything we do want to expose publicly. +impl RusticError { + pub fn new(kind: ErrorKind, guidance: impl Into) -> Self { + Self { + kind, + guidance: guidance.into().into(), + context: Vec::default(), + source: None, + code: None, + docs_url: None, + new_issue_url: None, + existing_issue_url: None, + severity: None, + status: None, + // `Backtrace::capture()` will check if backtrace has been enabled + // internally. It's zero cost if backtrace is disabled. + backtrace: Some(Backtrace::capture()), + } + } + + /// Expose the inner error kind. + /// + /// This is useful for matching on the error kind. + pub fn into_inner(self) -> ErrorKind { + self.kind + } + + /// Checks if the error is due to an incorrect password + pub fn is_incorrect_password(&self) -> bool { + matches!(self.kind, ErrorKind::Password) + } + + pub fn from( + error: T, + kind: ErrorKind, + ) -> Self { + Self { + kind, + guidance: error.to_string().into(), + context: Vec::default(), + source: Some(Box::new(error)), + code: None, + docs_url: None, + new_issue_url: None, + existing_issue_url: None, + severity: None, + status: None, + // `Backtrace::capture()` will check if backtrace has been enabled + // internally. It's zero cost if backtrace is disabled. + backtrace: Some(Backtrace::capture()), + } + } + + pub fn add_context(mut self, key: &'static str, value: impl Into) -> Self { + self.context.push((key, value.into())); + self + } } -/// [`NodeErrorKind`] describes the errors that can be returned by an action utilizing a node in Backends -#[derive(Error, Debug, Display)] -pub enum NodeErrorKind { - /// Parsing integer failed: `{0:?}` - FromParseIntError(#[from] ParseIntError), - /// Unexpected EOF - #[cfg(not(windows))] - UnexpectedEOF, - /// Invalid unicode - #[cfg(not(windows))] - InvalidUnicode, - /// Unrecognized Escape - #[cfg(not(windows))] - UnrecognizedEscape, +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Severity { + Info, + Warning, + Error, + Fatal, } -/// [`StdInErrorKind`] describes the errors that can be returned while dealing IO from CLI -#[derive(Error, Debug, Display)] -pub enum StdInErrorKind { - /// error reading from stdin: `{0:?}` - StdInError(#[from] std::io::Error), +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Status { + Permanent, + Temporary, + Persistent, } -/// [`ArchiverErrorKind`] describes the errors that can be returned from the archiver -#[derive(Error, Debug, Display)] -pub enum ArchiverErrorKind { - /// tree stack empty - TreeStackEmpty, - /// cannot open file - OpeningFileFailed, - /// option should contain a value, but contained `None` - UnpackingTreeTypeOptionalFailed, - /// couldn't get size for archive: `{0:?}` - CouldNotGetSizeForArchive(#[from] BackendAccessErrorKind), - /// couldn't determine size for item in Archiver - CouldNotDetermineSize, - /// failed to save index: `{0:?}` - IndexSavingFailed(#[from] IndexErrorKind), - /// failed to save file in backend: `{0:?}` - FailedToSaveFileInBackend(#[from] CryptBackendErrorKind), - /// finalizing SnapshotSummary failed: `{0:?}` - FinalizingSnapshotSummaryFailed(#[from] SnapshotFileErrorKind), - /// [`PackerErrorKind`] - #[error(transparent)] - FromPacker(#[from] PackerErrorKind), - /// [`TreeErrorKind`] - #[error(transparent)] - FromTree(#[from] TreeErrorKind), - /// [`ConfigFileErrorKind`] - #[error(transparent)] - FromConfigFile(#[from] ConfigFileErrorKind), - /// [`std::io::Error`] - #[error(transparent)] - FromStdIo(#[from] std::io::Error), - /// [`StripPrefixError`] - #[error(transparent)] - FromStripPrefix(#[from] StripPrefixError), - /// conversion from `u64` to `usize` failed: `{0:?}` - ConversionFromU64ToUsizeFailed(TryFromIntError), +/// [`ErrorKind`] describes the errors that can happen while executing a high-level command. +/// +/// This is a non-exhaustive enum, so additional variants may be added in future. It is +/// recommended to match against the wildcard `_` instead of listing all possible variants, +/// to avoid problems when new variants are added. +#[non_exhaustive] +#[derive(thiserror::Error, Debug, displaydoc::Display)] +pub enum ErrorKind { + /// Backend Error + Backend, + /// IO Error + Io, + /// Password Error + Password, + /// Repository Error + Repository, + /// Command Error + Command, + /// Config Error + Config, + /// Index Error + Index, + /// Key Error + Key, + /// Blob Error + Blob, + /// Crypto Error + Crypto, + /// Compression Error + Compression, + /// Parsing Error + Parsing, + /// Conversion Error + Conversion, + /// Permission Error + Permission, + /// Polynomial Error + Polynomial, + // /// The repository password is incorrect. Please try again. + // IncorrectRepositoryPassword, + // /// No repository given. Please use the --repository option. + // NoRepositoryGiven, + // /// No password given. Please use one of the --password-* options. + // NoPasswordGiven, + // /// warm-up command must contain %id! + // NoIDSpecified, + // /// error opening password file `{0:?}` + // OpeningPasswordFileFailed(std::io::Error), + // /// No repository config file found. Is there a repo at `{0}`? + // NoRepositoryConfigFound(String), + // /// More than one repository config file at `{0}`. Aborting. + // MoreThanOneRepositoryConfig(String), + // /// keys from repo and repo-hot do not match for `{0}`. Aborting. + // KeysDontMatchForRepositories(String), + // /// repository is a hot repository!\nPlease use as --repo-hot in combination with the normal repo. Aborting. + // HotRepositoryFlagMissing, + // /// repo-hot is not a hot repository! Aborting. + // IsNotHotRepository, + // /// incorrect password! + // IncorrectPassword, + // /// error running the password command + // PasswordCommandExecutionFailed, + // /// error reading password from command + // ReadingPasswordFromCommandFailed, + // /// running command `{0}`:`{1}` was not successful: `{2}` + // CommandExecutionFailed(String, String, std::io::Error), + // /// running command {0}:{1} returned status: `{2}` + // CommandErrorStatus(String, String, ExitStatus), + // /// error listing the repo config file + // ListingRepositoryConfigFileFailed, + // /// error listing the repo keys + // ListingRepositoryKeysFailed, + // /// error listing the hot repo keys + // ListingHotRepositoryKeysFailed, + // /// error accessing config file + // AccessToConfigFileFailed, + // /// Thread pool build error: `{0:?}` + // FromThreadPoolbilderError(rayon::ThreadPoolBuildError), + // /// reading Password failed: `{0:?}` + // ReadingPasswordFromReaderFailed(std::io::Error), + // /// reading Password from prompt failed: `{0:?}` + // ReadingPasswordFromPromptFailed(std::io::Error), + // /// Config file already exists. Aborting. + // ConfigFileExists, + // /// did not find id `{0}` in index + // IdNotFound(BlobId), + // /// no suitable backend type found + // NoBackendTypeGiven, + // /// Hex decoding error: `{0:?}` + // HexError(hex::FromHexError), } -/// [`VfsErrorKind`] describes the errors that can be returned from the Virtual File System -#[derive(Error, Debug, Display)] -pub enum VfsErrorKind { - /// No directory entries for symlink found: `{0:?}` - NoDirectoryEntriesForSymlinkFound(OsString), - /// Directory exists as non-virtual directory - DirectoryExistsAsNonVirtual, - /// Only normal paths allowed - OnlyNormalPathsAreAllowed, - /// Name `{0:?}`` doesn't exist - NameDoesNotExist(OsString), +// TODO: Possible more general categories for errors for RusticErrorKind (WIP): +// +// - **JSON Parsing Errors**: e.g., `serde_json::Error` +// - **Version Errors**: e.g., `VersionNotSupported`, `CannotDowngrade` +// - **Compression Errors**: e.g., `NoCompressionV1Repo`, `CompressionLevelNotSupported` +// - **Size Errors**: e.g., `SizeTooLarge` +// - **File and Path Errors**: e.g., `ErrorCreating`, `ErrorCollecting`, `ErrorSettingLength` +// - **Thread Pool Errors**: e.g., `rayon::ThreadPoolBuildError` +// - **Conversion Errors**: e.g., `ConversionFromIntFailed` +// - **Permission Errors**: e.g., `NotAllowedWithAppendOnly` +// - **Parsing Errors**: e.g., `shell_words::ParseError` +// - **Cryptographic Errors**: e.g., `DataDecryptionFailed`, `DataEncryptionFailed`, `CryptoKeyTooShort` +// - **Polynomial Errors**: e.g., `NoSuitablePolynomialFound` +// - **File Handling Errors**: e.g., `TransposingOptionResultFailed`, `ConversionFromU64ToUsizeFailed` +// - **ID Processing Errors**: e.g., `HexError` +// - **Repository Errors**: general repository-related errors +// - **Backend Access Errors**: e.g., `BackendNotSupported`, `BackendLoadError`, `NoSuitableIdFound`, `IdNotUnique` +// - **Rclone Errors**: e.g., `NoOutputForRcloneVersion`, `NoStdOutForRclone`, `RCloneExitWithBadStatus` +// - **REST API Errors**: e.g., `NotSupportedForRetry`, `UrlParsingFailed` + +pub mod immut_str { + //! Copyright 2024 Cloudflare, Inc. + //! + //! Licensed under the Apache License, Version 2.0 (the "License"); + //! you may not use this file except in compliance with the License. + //! You may obtain a copy of the License at + //! + //! http://www.apache.org/licenses/LICENSE-2.0 + //! + //! Unless required by applicable law or agreed to in writing, software + //! distributed under the License is distributed on an "AS IS" BASIS, + //! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + //! See the License for the specific language governing permissions and + //! limitations under the License. + //! + //! Taken from + + use std::fmt; + + /// A data struct that holds either immutable string or reference to static str. + /// Compared to String or `Box`, it avoids memory allocation on static str. + #[derive(Debug, PartialEq, Eq, Clone)] + pub enum ImmutStr { + Static(&'static str), + Owned(Box), + } + + impl ImmutStr { + #[inline] + pub fn as_str(&self) -> &str { + match self { + ImmutStr::Static(s) => s, + ImmutStr::Owned(s) => s.as_ref(), + } + } + + pub fn is_owned(&self) -> bool { + match self { + ImmutStr::Static(_) => false, + ImmutStr::Owned(_) => true, + } + } + } + + impl fmt::Display for ImmutStr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } + } + + impl From<&'static str> for ImmutStr { + fn from(s: &'static str) -> Self { + ImmutStr::Static(s) + } + } + + impl From for ImmutStr { + fn from(s: String) -> Self { + ImmutStr::Owned(s.into_boxed_str()) + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn test_static_vs_owned() { + let s: ImmutStr = "test".into(); + assert!(!s.is_owned()); + let s: ImmutStr = "test".to_string().into(); + assert!(s.is_owned()); + } + } } -trait RusticErrorMarker: Error {} - -impl RusticErrorMarker for CryptoErrorKind {} -impl RusticErrorMarker for PolynomialErrorKind {} -impl RusticErrorMarker for IdErrorKind {} -impl RusticErrorMarker for RepositoryErrorKind {} -impl RusticErrorMarker for IndexErrorKind {} -impl RusticErrorMarker for BackendAccessErrorKind {} -impl RusticErrorMarker for ConfigFileErrorKind {} -impl RusticErrorMarker for KeyFileErrorKind {} -impl RusticErrorMarker for PackFileErrorKind {} -impl RusticErrorMarker for SnapshotFileErrorKind {} -impl RusticErrorMarker for PackerErrorKind {} -impl RusticErrorMarker for FileErrorKind {} -impl RusticErrorMarker for TreeErrorKind {} -impl RusticErrorMarker for CacheBackendErrorKind {} -impl RusticErrorMarker for CryptBackendErrorKind {} -impl RusticErrorMarker for IgnoreErrorKind {} -impl RusticErrorMarker for LocalDestinationErrorKind {} -impl RusticErrorMarker for NodeErrorKind {} -impl RusticErrorMarker for StdInErrorKind {} -impl RusticErrorMarker for ArchiverErrorKind {} -impl RusticErrorMarker for CommandErrorKind {} -impl RusticErrorMarker for VfsErrorKind {} -impl RusticErrorMarker for std::io::Error {} - -impl From for RusticError -where - E: RusticErrorMarker, - RusticErrorKind: From, -{ - fn from(value: E) -> Self { - Self(RusticErrorKind::from(value)) +#[cfg(test)] +mod tests { + use std::sync::LazyLock; + + use super::*; + + static TEST_ERROR: LazyLock = LazyLock::new(|| RusticError { + kind: ErrorKind::Io, + guidance: + "A file could not be read, make sure the file is existing and readable by the system." + .to_string(), + status: Some(Status::Permanent), + severity: Some(Severity::Error), + code: Some("E001".to_string().into()), + context: vec![ + ("path", "/path/to/file".to_string()), + ("called", "used s3 backend".to_string()), + ], + source: Some(Box::new(std::io::Error::new( + std::io::ErrorKind::Other, + "networking error", + ))), + backtrace: Some(Backtrace::disabled()), + docs_url: None, + new_issue_url: None, + existing_issue_url: None, + }); + + #[test] + fn test_error_display() { + todo!("Implement test_error_display"); + } + + #[test] + fn test_error_debug() { + todo!("Implement test_error_debug"); } } From 7cdd50e8fcbf8c3b8eed2b45d468f1f5339befe4 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Wed, 23 Oct 2024 18:44:51 +0200 Subject: [PATCH 002/129] move error enums to their local files --- crates/backend/src/choose.rs | 7 ++ crates/backend/src/local.rs | 51 ++++++++- crates/backend/src/rclone.rs | 26 ++++- crates/backend/src/rest.rs | 27 ++++- crates/core/src/archiver.rs | 20 ++++ crates/core/src/backend.rs | 111 +++++++++++++++---- crates/core/src/backend/cache.rs | 21 ++++ crates/core/src/backend/ignore.rs | 38 +++++++ crates/core/src/backend/local_destination.rs | 83 +++++++++++++- crates/core/src/backend/node.rs | 60 ++++++++++ crates/core/src/blob/packer.rs | 16 +++ crates/core/src/blob/tree.rs | 31 ++++++ crates/core/src/chunker.rs | 10 +- crates/core/src/commands.rs | 44 ++++++++ crates/core/src/commands/check.rs | 94 ++++++++++++++++ crates/core/src/commands/config.rs | 23 ++++ crates/core/src/crypto.rs | 20 +++- crates/core/src/index.rs | 20 +++- crates/core/src/lib.rs | 2 +- crates/core/src/repofile/configfile.rs | 20 ++++ crates/core/src/repofile/keyfile.rs | 38 +++++++ crates/core/src/repofile/packfile.rs | 24 ++++ crates/core/src/repofile/snapshotfile.rs | 32 ++++++ crates/core/src/repository.rs | 6 +- crates/core/src/repository/command_input.rs | 36 +++++- crates/core/src/repository/warm_up.rs | 10 ++ crates/core/src/vfs.rs | 15 +++ 27 files changed, 836 insertions(+), 49 deletions(-) diff --git a/crates/backend/src/choose.rs b/crates/backend/src/choose.rs index 51dadcea..7455b572 100644 --- a/crates/backend/src/choose.rs +++ b/crates/backend/src/choose.rs @@ -24,6 +24,13 @@ use crate::rest::RestBackend; #[cfg(feature = "clap")] use clap::ValueHint; +/// [`ChooseBackendErrorKind`] describes the errors that can be returned by the choose backend +#[derive(thiserror::Error, Debug, displaydoc::Display)] +#[non_exhaustive] +pub enum ChooseBackendErrorKind {} + +pub(crate) type ChooseBackendResult = Result; + /// Options for a backend. #[cfg_attr(feature = "clap", derive(clap::Parser))] #[cfg_attr(feature = "merge", derive(conflate::Merge))] diff --git a/crates/backend/src/local.rs b/crates/backend/src/local.rs index 62c00776..977f5c18 100644 --- a/crates/backend/src/local.rs +++ b/crates/backend/src/local.rs @@ -14,9 +14,54 @@ use rustic_core::{ CommandInput, FileType, Id, ReadBackend, RusticResult, WriteBackend, ALL_FILE_TYPES, }; -use rustic_core::{CommandInput, FileType, Id, ReadBackend, WriteBackend, ALL_FILE_TYPES}; - -use crate::error::LocalBackendErrorKind; +/// [`LocalBackendErrorKind`] describes the errors that can be returned by an action on the filesystem in Backends +#[derive(thiserror::Error, Debug, displaydoc::Display)] +#[non_exhaustive] +pub enum LocalBackendErrorKind { + /// directory creation failed: `{0:?}` + DirectoryCreationFailed(std::io::Error), + /// querying metadata failed: `{0:?}` + QueryingMetadataFailed(std::io::Error), + /// querying WalkDir metadata failed: `{0:?}` + QueryingWalkDirMetadataFailed(walkdir::Error), + /// execution of command failed: `{0:?}` + CommandExecutionFailed(std::io::Error), + /// command was not successful for filename {file_name}, type {file_type}, id {id}: {status} + CommandNotSuccessful { + /// File name + file_name: String, + /// File type + file_type: String, + /// Item ID + id: String, + /// Exit status + status: ExitStatus, + }, + /// error building automaton `{0:?}` + FromAhoCorasick(aho_corasick::BuildError), + /// {0:?} + #[error(transparent)] + FromTryIntError(TryFromIntError), + /// {0:?} + #[error(transparent)] + FromWalkdirError(walkdir::Error), + /// removing file failed: `{0:?}` + FileRemovalFailed(std::io::Error), + /// opening file failed: `{0:?}` + OpeningFileFailed(std::io::Error), + /// setting file length failed: `{0:?}` + SettingFileLengthFailed(std::io::Error), + /// can't jump to position in file: `{0:?}` + CouldNotSeekToPositionInFile(std::io::Error), + /// couldn't write to buffer: `{0:?}` + CouldNotWriteToBuffer(std::io::Error), + /// reading file contents failed: `{0:?}` + ReadingContentsOfFileFailed(std::io::Error), + /// reading exact length of file contents failed: `{0:?}` + ReadingExactLengthOfFileFailed(std::io::Error), + /// failed to sync OS Metadata to disk: `{0:?}` + SyncingOfOsMetadataFailed(std::io::Error), +} /// A local backend. #[derive(Clone, Debug)] diff --git a/crates/backend/src/rclone.rs b/crates/backend/src/rclone.rs index 22a4133f..8d66e434 100644 --- a/crates/backend/src/rclone.rs +++ b/crates/backend/src/rclone.rs @@ -18,9 +18,29 @@ use crate::rest::RestBackend; use rustic_core::{CommandInput, FileType, Id, ReadBackend, RusticResult, WriteBackend}; -use crate::{error::RcloneErrorKind, rest::RestBackend}; - -use rustic_core::{CommandInput, FileType, Id, ReadBackend, WriteBackend}; +/// [`RcloneErrorKind`] describes the errors that can be returned by a backend provider +#[derive(thiserror::Error, Debug, displaydoc::Display)] +#[non_exhaustive] +pub enum RcloneErrorKind { + /// 'rclone version' doesn't give any output + NoOutputForRcloneVersion, + /// cannot get stdout of rclone + NoStdOutForRclone, + /// rclone exited with `{0:?}` + RCloneExitWithBadStatus(ExitStatus), + /// url must start with http:\/\/! url: {0:?} + UrlNotStartingWithHttp(String), + /// StdIo Error: `{0:?}` + #[error(transparent)] + FromIoError(std::io::Error), + /// utf8 error: `{0:?}` + #[error(transparent)] + FromUtf8Error(Utf8Error), + /// error parsing version number from `{0:?}` + FromParseVersion(String), + /// Using rclone without authentication! Upgrade to rclone >= 1.52.2 (current version: `{0}`)! + RCloneWithoutAuthentication(String), +} pub(super) mod constants { /// The default command called if no other is specified diff --git a/crates/backend/src/rest.rs b/crates/backend/src/rest.rs index 9bc4852b..46bbb039 100644 --- a/crates/backend/src/rest.rs +++ b/crates/backend/src/rest.rs @@ -3,6 +3,7 @@ use std::time::Duration; use backoff::{backoff::Backoff, ExponentialBackoff, ExponentialBackoffBuilder}; use bytes::Bytes; +use displaydoc::Display; use log::{trace, warn}; use reqwest::{ blocking::{Client, ClientBuilder, Response}, @@ -10,11 +11,33 @@ use reqwest::{ Url, }; use serde::Deserialize; - -use crate::error::RestErrorKind; +use thiserror::Error; use rustic_core::{FileType, Id, ReadBackend, WriteBackend}; +/// [`RestErrorKind`] describes the errors that can be returned while dealing with the REST API +#[derive(Error, Debug, Display)] +#[non_exhaustive] +pub enum RestErrorKind { + /// value `{0:?}` not supported for option retry! + NotSupportedForRetry(String), + /// parsing failed for url: `{0:?}` + UrlParsingFailed(url::ParseError), + #[cfg(feature = "rest")] + /// requesting resource failed: `{0:?}` + RequestingResourceFailed(reqwest::Error), + /// couldn't parse duration in humantime library: `{0:?}` + CouldNotParseDuration(humantime::DurationError), + #[cfg(feature = "rest")] + /// backoff failed: {0:?} + BackoffError(backoff::Error), + #[cfg(feature = "rest")] + /// Failed to build HTTP client: `{0:?}` + BuildingClientFailed(reqwest::Error), + /// joining URL failed on: {0:?} + JoiningUrlFailed(url::ParseError), +} + mod consts { /// Default number of retries pub(super) const DEFAULT_RETRY: usize = 5; diff --git a/crates/core/src/archiver.rs b/crates/core/src/archiver.rs index 9a304a7b..7c5ed69e 100644 --- a/crates/core/src/archiver.rs +++ b/crates/core/src/archiver.rs @@ -21,6 +21,25 @@ use crate::{ Progress, RusticResult, }; +/// [`ArchiverErrorKind`] describes the errors that can be returned from the archiver +#[derive(thiserror::Error, Debug, displaydoc::Display)] +#[non_exhaustive] +pub enum ArchiverErrorKind { + /// tree stack empty + TreeStackEmpty, + /// cannot open file or directory `{path}` + OpeningFileFailed { + /// path of the file + path: PathBuf, + }, + /// option should contain a value, but contained `None` + UnpackingTreeTypeOptionalFailed, + /// couldn't determine size for item in Archiver + CouldNotDetermineSize, +} + +pub(crate) type ArchiverResult = Result; + /// The `Archiver` is responsible for archiving files and trees. /// It will read the file, chunk it, and write the chunks to the backend. /// @@ -84,6 +103,7 @@ impl<'a, BE: DecryptFullBackend, I: ReadGlobalIndex> Archiver<'a, BE, I> { let file_archiver = FileArchiver::new(be.clone(), index, indexer.clone(), config)?; let tree_archiver = TreeArchiver::new(be.clone(), index, indexer.clone(), config, summary)?; + Ok(Self { file_archiver, tree_archiver, diff --git a/crates/core/src/backend.rs b/crates/core/src/backend.rs index b232ceb1..37ba1fa8 100644 --- a/crates/core/src/backend.rs +++ b/crates/core/src/backend.rs @@ -24,11 +24,78 @@ use serde_derive::{Deserialize, Serialize}; use crate::{ backend::node::{Metadata, Node, NodeType}, - error::{BackendAccessErrorKind, RusticErrorKind}, + error::RusticResult, id::Id, RusticResult, }; +// #[derive(thiserror::Error, Debug, displaydoc::Display)] +// /// Experienced an error in the backend: `{0}` +// pub struct BackendDynError(pub Box); + +/// [`BackendErrorKind`] describes the errors that can be returned by the various Backends +#[derive(thiserror::Error, Debug, displaydoc::Display)] +#[non_exhaustive] +pub enum BackendErrorKind { + /// backend `{0:?}` is not supported! + BackendNotSupported(String), + /// no suitable id found for `{0}` + NoSuitableIdFound(String), + /// id `{0}` is not unique + IdNotUnique(String), + /// creating data in backend failed + CreatingDataOnBackendFailed, + /// writing bytes to backend failed + WritingBytesToBackendFailed, + /// removing data from backend failed + RemovingDataFromBackendFailed, + /// failed to list files on Backend + ListingFilesOnBackendFailed, + /// Path is not allowed: `{0:?}` + PathNotAllowed(PathBuf), + /// Backend location not convertible: `{location}` + BackendLocationNotConvertible { location: String }, +} + +/// [`CryptBackendErrorKind`] describes the errors that can be returned by a Decryption action in Backends +#[derive(thiserror::Error, Debug, displaydoc::Display)] +#[non_exhaustive] +pub enum CryptBackendErrorKind { + /// decryption not supported for backend + DecryptionNotSupportedForBackend, + /// length of uncompressed data does not match! + LengthOfUncompressedDataDoesNotMatch, + /// failed to read encrypted data during full read + DecryptionInFullReadFailed, + /// failed to read encrypted data during partial read + DecryptionInPartialReadFailed, + /// decrypting from backend failed + DecryptingFromBackendFailed, + /// deserializing from bytes of JSON Text failed: `{0:?}` + DeserializingFromBytesOfJsonTextFailed(serde_json::Error), + /// failed to write data in crypt backend + WritingDataInCryptBackendFailed, + /// failed to list Ids + ListingIdsInDecryptionBackendFailed, + /// writing full hash failed in CryptBackend + WritingFullHashFailed, + /// decoding Zstd compressed data failed: `{0:?}` + DecodingZstdCompressedDataFailed(std::io::Error), + /// Serializing to JSON byte vector failed: `{0:?}` + SerializingToJsonByteVectorFailed(serde_json::Error), + /// encrypting data failed + EncryptingDataFailed, + /// Compressing and appending data failed: `{0:?}` + CopyEncodingDataFailed(std::io::Error), + /// conversion for integer failed: `{0:?}` + IntConversionFailed(TryFromIntError), + /// Extra verification failed: After decrypting and decompressing the data changed! + ExtraVerificationFailed, +} + +pub(crate) type BackendResult = Result; +pub(crate) type CryptBackendResult = Result; + /// All [`FileType`]s which are located in separated directories pub const ALL_FILE_TYPES: [FileType; 4] = [ FileType::Key, @@ -95,7 +162,7 @@ pub trait ReadBackend: Send + Sync + 'static { /// # Errors /// /// If the files could not be listed. - fn list_with_size(&self, tpe: FileType) -> Result>; + fn list_with_size(&self, tpe: FileType) -> RusticResult>; /// Lists all files of the given type. /// @@ -106,7 +173,7 @@ pub trait ReadBackend: Send + Sync + 'static { /// # Errors /// /// If the files could not be listed. - fn list(&self, tpe: FileType) -> Result> { + fn list(&self, tpe: FileType) -> RusticResult> { Ok(self .list_with_size(tpe)? .into_iter() @@ -124,7 +191,7 @@ pub trait ReadBackend: Send + Sync + 'static { /// # Errors /// /// If the file could not be read. - fn read_full(&self, tpe: FileType, id: &Id) -> Result; + fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult; /// Reads partial data of the given file. /// @@ -146,7 +213,7 @@ pub trait ReadBackend: Send + Sync + 'static { cacheable: bool, offset: u32, length: u32, - ) -> Result; + ) -> RusticResult; /// Specify if the backend needs a warming-up of files before accessing them. fn needs_warm_up(&self) -> bool { @@ -163,7 +230,7 @@ pub trait ReadBackend: Send + Sync + 'static { /// # Errors /// /// If the file could not be read. - fn warm_up(&self, _tpe: FileType, _id: &Id) -> Result<()> { + fn warm_up(&self, _tpe: FileType, _id: &Id) -> RusticResult<()> { Ok(()) } } @@ -299,7 +366,7 @@ pub trait WriteBackend: ReadBackend { /// # Returns /// /// The result of the creation. - fn create(&self) -> Result<()> { + fn create(&self) -> RusticResult<()> { Ok(()) } @@ -319,7 +386,7 @@ pub trait WriteBackend: ReadBackend { /// # Returns /// /// The result of the write. - fn write_bytes(&self, tpe: FileType, id: &Id, cacheable: bool, buf: Bytes) -> Result<()>; + fn write_bytes(&self, tpe: FileType, id: &Id, cacheable: bool, buf: Bytes) -> RusticResult<()>; /// Removes the given file. /// @@ -336,7 +403,7 @@ pub trait WriteBackend: ReadBackend { /// # Returns /// /// The result of the removal. - fn remove(&self, tpe: FileType, id: &Id, cacheable: bool) -> Result<()>; + fn remove(&self, tpe: FileType, id: &Id, cacheable: bool) -> RusticResult<()>; } #[cfg(test)] @@ -345,8 +412,8 @@ mock! { impl ReadBackend for Backend{ fn location(&self) -> String; - fn list_with_size(&self, tpe: FileType) -> Result>; - fn read_full(&self, tpe: FileType, id: &Id) -> Result; + fn list_with_size(&self, tpe: FileType) -> RusticResult>; + fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult; fn read_partial( &self, tpe: FileType, @@ -354,24 +421,24 @@ mock! { cacheable: bool, offset: u32, length: u32, - ) -> Result; + ) -> RusticResult; } impl WriteBackend for Backend { - fn create(&self) -> Result<()>; - fn write_bytes(&self, tpe: FileType, id: &Id, cacheable: bool, buf: Bytes) -> Result<()>; - fn remove(&self, tpe: FileType, id: &Id, cacheable: bool) -> Result<()>; + fn create(&self) -> RusticResult<()>; + fn write_bytes(&self, tpe: FileType, id: &Id, cacheable: bool, buf: Bytes) -> RusticResult<()>; + fn remove(&self, tpe: FileType, id: &Id, cacheable: bool) -> RusticResult<()>; } } impl WriteBackend for Arc { - fn create(&self) -> Result<()> { + fn create(&self) -> RusticResult<()> { self.deref().create() } - fn write_bytes(&self, tpe: FileType, id: &Id, cacheable: bool, buf: Bytes) -> Result<()> { + fn write_bytes(&self, tpe: FileType, id: &Id, cacheable: bool, buf: Bytes) -> RusticResult<()> { self.deref().write_bytes(tpe, id, cacheable, buf) } - fn remove(&self, tpe: FileType, id: &Id, cacheable: bool) -> Result<()> { + fn remove(&self, tpe: FileType, id: &Id, cacheable: bool) -> RusticResult<()> { self.deref().remove(tpe, id, cacheable) } } @@ -380,13 +447,13 @@ impl ReadBackend for Arc { fn location(&self) -> String { self.deref().location() } - fn list_with_size(&self, tpe: FileType) -> Result> { + fn list_with_size(&self, tpe: FileType) -> RusticResult> { self.deref().list_with_size(tpe) } - fn list(&self, tpe: FileType) -> Result> { + fn list(&self, tpe: FileType) -> RusticResult> { self.deref().list(tpe) } - fn read_full(&self, tpe: FileType, id: &Id) -> Result { + fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult { self.deref().read_full(tpe, id) } fn read_partial( @@ -396,7 +463,7 @@ impl ReadBackend for Arc { cacheable: bool, offset: u32, length: u32, - ) -> Result { + ) -> RusticResult { self.deref() .read_partial(tpe, id, cacheable, offset, length) } diff --git a/crates/core/src/backend/cache.rs b/crates/core/src/backend/cache.rs index e1315bd1..bc1e1c39 100644 --- a/crates/core/src/backend/cache.rs +++ b/crates/core/src/backend/cache.rs @@ -18,6 +18,27 @@ use crate::{ repofile::configfile::RepositoryId, }; +/// [`CacheBackendErrorKind`] describes the errors that can be returned by a Caching action in Backends +#[derive(thiserror::Error, Debug, displaydoc::Display)] +pub enum CacheBackendErrorKind { + /// Cache directory could not be determined, please set the environment variable XDG_CACHE_HOME or HOME! + NoCacheDirectory, + /// Error in cache backend {context} for {tpe:?} with {id}: {source} + Io { + context: String, + source: std::io::Error, + tpe: Option, + id: Id, + }, + /// Ensuring tag failed for cache directory {path}: {source} + EnsureTagFailed { + source: std::io::Error, + path: PathBuf, + }, +} + +pub(crate) type CacheBackendResult = Result; + /// Backend that caches data. /// /// This backend caches data in a directory. diff --git a/crates/core/src/backend/ignore.rs b/crates/core/src/backend/ignore.rs index 3f417902..b69e8d23 100644 --- a/crates/core/src/backend/ignore.rs +++ b/crates/core/src/backend/ignore.rs @@ -31,6 +31,44 @@ use crate::{ error::RusticResult, }; +/// [`IgnoreErrorKind`] describes the errors that can be returned by a Ignore action in Backends +#[derive(thiserror::Error, Debug, displaydoc::Display)] +pub enum IgnoreErrorKind { + /// generic Ignore error: `{0:?}` + GenericError(ignore::Error), + /// Error reading glob file `{file:?}`: `{source:?}` + ErrorGlob { + file: PathBuf, + source: std::io::Error, + }, + /// Unable to open file `{file:?}`: `{source:?}` + UnableToOpenFile { + file: PathBuf, + source: std::io::Error, + }, + /// Error getting xattrs for `{path:?}`: `{source:?}` + ErrorXattr { + path: PathBuf, + source: std::io::Error, + }, + /// Error reading link target for `{path:?}`: `{source:?}` + ErrorLink { + path: PathBuf, + source: std::io::Error, + }, + #[cfg(not(windows))] + /// Error converting ctime `{ctime}` and ctime_nsec `{ctime_nsec}` to Utc Timestamp: `{source:?}` + CtimeConversionToTimestampFailed { + ctime: i64, + ctime_nsec: i64, + source: ignore::Error, + }, + /// Error acquiring metadata for `{name}`: `{source:?}` + AcquiringMetadataFailed { name: String, source: ignore::Error }, +} + +pub(crate) type IgnoreResult = Result; + /// A [`LocalSource`] is a source from local paths which is used to be read from (i.e. to backup it). #[derive(Debug)] pub struct LocalSource { diff --git a/crates/core/src/backend/local_destination.rs b/crates/core/src/backend/local_destination.rs index 8c2ea2d4..99fe5a14 100644 --- a/crates/core/src/backend/local_destination.rs +++ b/crates/core/src/backend/local_destination.rs @@ -25,11 +25,84 @@ use nix::{ use crate::backend::ignore::mapper::map_mode_from_go; #[cfg(not(windows))] use crate::backend::node::NodeType; -use crate::{ - backend::node::{ExtendedAttribute, Metadata, Node}, - error::LocalDestinationErrorKind, - RusticResult, -}; +use crate::backend::node::{ExtendedAttribute, Metadata, Node}; +use crate::error::RusticResult; + +/// [`LocalDestinationErrorKind`] describes the errors that can be returned by an action on the filesystem in Backends +#[derive(thiserror::Error, Debug, displaydoc::Display)] +pub enum LocalDestinationErrorKind { + /// directory creation failed: `{0:?}` + DirectoryCreationFailed(std::io::Error), + /// file `{0:?}` should have a parent + FileDoesNotHaveParent(PathBuf), + /// DeviceID could not be converted to other type `{target}` of device `{device}`: `{source}` + DeviceIdConversionFailed { + target: String, + device: u64, + source: TryFromIntError, + }, + /// Length conversion failed for `{target}` of length `{length}`: `{source}` + LengthConversionFailed { + target: String, + length: u64, + source: TryFromIntError, + }, + /// [`walkdir::Error`] + #[error(transparent)] + FromWalkdirError(walkdir::Error), + /// [`Errno`] + #[error(transparent)] + #[cfg(not(windows))] + FromErrnoError(Errno), + /// listing xattrs on `{path:?}`: `{source:?}` + #[cfg(not(any(windows, target_os = "openbsd")))] + ListingXattrsFailed { + path: PathBuf, + source: std::io::Error, + }, + /// setting xattr `{name}` on `{filename:?}` with `{source:?}` + #[cfg(not(any(windows, target_os = "openbsd")))] + SettingXattrFailed { + name: String, + filename: PathBuf, + source: std::io::Error, + }, + /// getting xattr `{name}` on `{filename:?}` with `{source:?}` + #[cfg(not(any(windows, target_os = "openbsd")))] + GettingXattrFailed { + name: String, + filename: PathBuf, + source: std::io::Error, + }, + /// removing directories failed: `{0:?}` + DirectoryRemovalFailed(std::io::Error), + /// removing file failed: `{0:?}` + FileRemovalFailed(std::io::Error), + /// setting time metadata failed: `{0:?}` + SettingTimeMetadataFailed(std::io::Error), + /// opening file failed: `{0:?}` + OpeningFileFailed(std::io::Error), + /// setting file length failed: `{0:?}` + SettingFileLengthFailed(std::io::Error), + /// can't jump to position in file: `{0:?}` + CouldNotSeekToPositionInFile(std::io::Error), + /// couldn't write to buffer: `{0:?}` + CouldNotWriteToBuffer(std::io::Error), + /// reading exact length of file contents failed: `{0:?}` + ReadingExactLengthOfFileFailed(std::io::Error), + /// setting file permissions failed: `{0:?}` + #[cfg(not(windows))] + SettingFilePermissionsFailed(std::io::Error), + /// failed to symlink target `{linktarget:?}` from `{filename:?}` with `{source:?}` + #[cfg(not(windows))] + SymlinkingFailed { + linktarget: PathBuf, + filename: PathBuf, + source: std::io::Error, + }, +} + +pub(crate) type LocalDestinationResult = Result; #[derive(Clone, Debug)] /// Local destination, used when restoring. diff --git a/crates/core/src/backend/node.rs b/crates/core/src/backend/node.rs index 94fb7995..2b3e7304 100644 --- a/crates/core/src/backend/node.rs +++ b/crates/core/src/backend/node.rs @@ -29,6 +29,66 @@ use crate::error::NodeErrorKind; use crate::blob::{tree::TreeId, DataId}; +#[cfg(not(windows))] +/// [`NodeErrorKind`] describes the errors that can be returned by an action utilizing a node in Backends +#[derive(thiserror::Error, Debug, Display)] +#[non_exhaustive] +pub enum NodeErrorKind { + /// Unexpected EOF while parsing filename: `{file_name}` + #[cfg(not(windows))] + UnexpectedEOF { + /// The filename + file_name: String, + /// The remaining chars + chars: std::str::Chars, + }, + /// Invalid unicode + #[cfg(not(windows))] + InvalidUnicode { + /// The filename + file_name: String, + /// The unicode codepoint + unicode: u32, + /// The remaining chars + chars: std::str::Chars, + }, + /// Unrecognized Escape while parsing filename: `{file_name}` + #[cfg(not(windows))] + UnrecognizedEscape { + /// The filename + file_name: String, + /// The remaining chars + chars: std::str::Chars, + }, + /// Parsing hex chars {chars:?} failed for `{hex}` in filename: `{file_name}` : `{source}` + #[cfg(not(windows))] + ParsingHexFailed { + /// The filename + file_name: String, + /// The hex string + hex: String, + /// The remaining chars + chars: std::str::Chars, + /// The error that occurred + source: ParseIntError, + }, + /// Parsing unicode chars {chars:?} failed for `{target}` in filename: `{file_name}` : `{source}` + #[cfg(not(windows))] + ParsingUnicodeFailed { + /// The filename + file_name: String, + /// The target type + target: String, + /// The remaining chars + chars: std::str::Chars, + /// The error that occurred + source: ParseIntError, + }, +} + +#[cfg(not(windows))] +pub(crate) type NodeResult = Result; + #[derive( Default, Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Constructor, PartialOrd, Ord, )] diff --git a/crates/core/src/blob/packer.rs b/crates/core/src/blob/packer.rs index 62c82759..2042c900 100644 --- a/crates/core/src/blob/packer.rs +++ b/crates/core/src/blob/packer.rs @@ -29,6 +29,22 @@ use crate::{ }, }; +/// [`PackerErrorKind`] describes the errors that can be returned for a Packer +#[derive(thiserror::Error, Debug, displaydoc::Display)] +#[non_exhaustive] +pub enum PackerErrorKind { + /// getting total size failed + GettingTotalSizeFailed, + /// Conversion from `{from}` to `{to}` failed: {source} + ConversionFailed { + to: &'static str, + from: &'static str, + source: std::num::TryFromIntError, + }, +} + +pub(crate) type PackerResult = Result; + pub(super) mod constants { use std::time::Duration; diff --git a/crates/core/src/blob/tree.rs b/crates/core/src/blob/tree.rs index ffd03577..acb19eff 100644 --- a/crates/core/src/blob/tree.rs +++ b/crates/core/src/blob/tree.rs @@ -30,6 +30,37 @@ use crate::{ repofile::snapshotfile::SnapshotSummary, }; +/// [`TreeErrorKind`] describes the errors that can come up dealing with Trees +#[derive(thiserror::Error, Debug, displaydoc::Display)] +#[non_exhaustive] +pub enum TreeErrorKind { + /// blob `{0}` not found in index + BlobIdNotFound(TreeId), + /// `{0:?}` is not a directory + NotADirectory(OsString), + /// Path `{0:?}` not found + PathNotFound(OsString), + /// path should not contain current or parent dir + ContainsCurrentOrParentDirectory, + /// serde_json couldn't serialize the tree: `{0:?}` + SerializingTreeFailed(serde_json::Error), + /// serde_json couldn't deserialize tree from bytes of JSON text: `{0:?}` + DeserializingTreeFailed(serde_json::Error), + /// slice is not UTF-8: `{0:?}` + PathIsNotUtf8Conform(Utf8Error), + /// error in building nodestreamer: `{0:?}` + BuildingNodeStreamerFailed(ignore::Error), + /// failed to read file string from glob file: `{0:?}` + ReadingFileStringFromGlobsFailed(std::io::Error), + /// Error `{kind}` in tree streamer: `{source}` + Channel { + kind: &'static str, + source: Box, + }, +} + +pub(crate) type TreeResult = Result; + pub(super) mod constants { /// The maximum number of trees that are loaded in parallel pub(super) const MAX_TREE_LOADER: usize = 4; diff --git a/crates/core/src/chunker.rs b/crates/core/src/chunker.rs index fb6a300b..530ee7f0 100644 --- a/crates/core/src/chunker.rs +++ b/crates/core/src/chunker.rs @@ -7,9 +7,17 @@ use crate::{ polynom::{Polynom, Polynom64}, rolling_hash::{Rabin64, RollingHash64}, }, - error::{PolynomialErrorKind, RusticResult}, + error::RusticResult, }; +/// [`PolynomialErrorKind`] describes the errors that can happen while dealing with Polynomials +#[derive(thiserror::Error, Debug, displaydoc::Display)] +#[non_exhaustive] +pub enum PolynomialErrorKind { + /// no suitable polynomial found + NoSuitablePolynomialFound, +} + pub(super) mod constants { /// The Splitmask is used to determine if a chunk is a chunk boundary. pub(super) const SPLITMASK: u64 = (1u64 << 20) - 1; diff --git a/crates/core/src/commands.rs b/crates/core/src/commands.rs index e4d15a12..61c79cf0 100644 --- a/crates/core/src/commands.rs +++ b/crates/core/src/commands.rs @@ -1,5 +1,11 @@ //! The commands that can be run by the CLI. +use std::{num::TryFromIntError, path::PathBuf}; + +use chrono::OutOfRangeError; + +use crate::{backend::node::NodeType, blob::BlobId, repofile::packfile::PackId, RusticError}; + pub mod backup; /// The `cat` command. pub mod cat; @@ -20,3 +26,41 @@ pub mod repair; pub mod repoinfo; pub mod restore; pub mod snapshots; + +/// [`CommandErrorKind`] describes the errors that can happen while executing a high-level command +#[derive(thiserror::Error, Debug, displaydoc::Display)] +#[non_exhaustive] +pub enum CommandErrorKind { + /// path is no dir: `{0}` + PathIsNoDir(String), + /// used blobs are missing: blob `{0}` doesn't existing + BlobsMissing(BlobId), + /// used pack `{0}`: size does not match! Expected size: `{1}`, real size: `{2}` + PackSizeNotMatching(PackId, u32, u32), + /// used pack `{0}` does not exist! + PackNotExisting(PackId), + /// pack `{0}` got no decision what to do + NoDecision(PackId), + /// Bytesize parser failed: `{0}` + FromByteSizeParser(String), + /// --repack-uncompressed makes no sense for v1 repo! + RepackUncompressedRepoV1, + /// datetime out of range: `{0}` + FromOutOfRangeError(OutOfRangeError), + /// node type `{0:?}` not supported by dump + DumpNotSupported(NodeType), + /// error creating `{0:?}`: `{1:?}` + ErrorCreating(PathBuf, Box), + /// error collecting information for `{0:?}`: `{1:?}` + ErrorCollecting(PathBuf, Box), + /// error setting length for `{0:?}`: `{1:?}` + ErrorSettingLength(PathBuf, Box), + /// Conversion from integer failed: `{0:?}` + ConversionFromIntFailed(TryFromIntError), + /// Specify one of the keep-* options for forget! Please use keep-none to keep no snapshot. + NoKeepOption, + /// Checking the repository failed! + CheckFailed, +} + +pub(crate) type CommandResult = Result; diff --git a/crates/core/src/commands/check.rs b/crates/core/src/commands/check.rs index 443e828a..6b6cb3ca 100644 --- a/crates/core/src/commands/check.rs +++ b/crates/core/src/commands/check.rs @@ -32,6 +32,100 @@ use crate::{ TreeId, }; +#[non_exhaustive] +#[derive(thiserror::Error, Debug, displaydoc::Display)] +#[non_exhaustive] +pub enum CheckCheckCommandErrorKind { + /// error reading pack {id} : {source} + ErrorReadingPack { + id: PackId, + source: Box, + }, + /// cold file for hot file Type: {file_type:?}, Id: {id} does not exist + NoColdFile { id: Id, file_type: FileType }, + /// Type: {file_type:?}, Id: {id}: hot size: {size_hot}, actual size: {size} + HotFileSizeMismatch { + id: Id, + file_type: FileType, + size_hot: u32, + size: u32, + }, + /// hot file Type: {file_type:?}, Id: {id} is missing! + NoHotFile { id: Id, file_type: FileType }, + /// Error reading cached file Type: {file_type:?}, Id: {id} : {source} + ErrorReadingCache { + id: Id, + file_type: FileType, + source: Box, + }, + /// Error reading file Type: {file_type:?}, Id: {id} : {source} + ErrorReadingFile { + id: Id, + file_type: FileType, + source: Box, + }, + /// Cached file Type: {file_type:?}, Id: {id} is not identical to backend! + CacheMismatch { id: Id, file_type: FileType }, + /// pack {id}: No time is set! Run prune to correct this! + PackTimeNotSet { id: PackId }, + /// pack {id}: blob {blob_id} blob type does not match: type: {blob_type:?}, expected: {expected:?} + PackBlobTypesMismatch { + id: PackId, + blob_id: BlobId, + blob_type: BlobType, + expected: BlobType, + }, + /// pack {id}: blob {blob_id} offset in index: {offset}, expected: {expected} + PackBlobOffsetMismatch { + id: PackId, + blob_id: BlobId, + offset: u32, + expected: u32, + }, + /// pack {id} not referenced in index. Can be a parallel backup job. To repair: 'rustic repair index'. + PackNotReferenced { id: Id }, + /// pack {id}: size computed by index: {index_size}, actual size: {size}. To repair: 'rustic repair index'. + PackSizeMismatchIndex { id: Id, index_size: u32, size: u32 }, + /// pack {id} is referenced by the index but not present! To repair: 'rustic repair index'." + NoPack { id: PackId }, + /// file {file:?} doesn't have a content + FileHasNoContent { file: PathBuf }, + /// file {file:?} blob {blob_num} has null ID + FileBlobHasNullId { file: PathBuf, blob_num: usize }, + /// file {file:?} blob {blob_id} is missing in index + FileBlobNotInIndex { file: PathBuf, blob_id: Id }, + /// dir {dir:?} doesn't have a subtree + NoSubTree { dir: PathBuf }, + /// "dir {dir:?} subtree has null ID + NullSubTree { dir: PathBuf }, + /// pack {id}: data size does not match expected size. Read: {size} bytes, expected: {expected} bytes + PackSizeMismatch { + id: PackId, + size: usize, + expected: usize, + }, + /// pack {id}: Hash mismatch. Computed hash: {computed} + PackHashMismatch { id: PackId, computed: PackId }, + /// pack {id}: Header length in pack file doesn't match index. In pack: {length}, computed: {computed} + PackHeaderLengthMismatch { + id: PackId, + length: u32, + computed: u32, + }, + /// pack {id}: Header from pack file does not match the index + PackHeaderMismatchIndex { id: PackId }, + /// pack {id}, blob {blob_id}: Actual uncompressed length does not fit saved uncompressed length + PackBlobLengthMismatch { id: PackId, blob_id: BlobId }, + /// pack {id}, blob {blob_id}: Hash mismatch. Computed hash: {computed} + PackBlobHashMismatch { + id: PackId, + blob_id: BlobId, + computed: BlobId, + }, +} + +pub(crate) type CheckResult = Result<(), Vec>; + #[derive(Clone, Copy, Debug, Default)] #[non_exhaustive] /// Options to specify which subset of packs will be read diff --git a/crates/core/src/commands/config.rs b/crates/core/src/commands/config.rs index 195f8701..4f1e2446 100644 --- a/crates/core/src/commands/config.rs +++ b/crates/core/src/commands/config.rs @@ -10,6 +10,29 @@ use crate::{ repository::{Open, Repository}, }; +#[non_exhaustive] +#[derive(thiserror::Error, Debug, displaydoc::Display)] +pub enum ConfigCommandErrorKind { + /// Not allowed on an append-only repository: `{0}` + NotAllowedWithAppendOnly(String), + /// compression level `{0}` is not supported for repo v1 + NoCompressionV1Repo(i32), + /// version `{0}` is not supported. Allowed values: {1:?} + VersionNotSupported(u32, RangeInclusive), + /// compression level `{0}` is not supported. Allowed values: `{1:?}` + CompressionLevelNotSupported(i32, RangeInclusive), + /// cannot downgrade version from `{0}` to `{1}` + CannotDowngrade(u32, u32), + /// Size is too large: `{0}` + SizeTooLarge(ByteSize), + /// min_packsize_tolerate_percent must be <= 100 + MinPackSizeTolerateWrong, + /// max_packsize_tolerate_percent must be >= 100 or 0" + MaxPackSizeTolerateWrong, +} + +pub(crate) type ConfigCommandResult = Result; + /// Apply the [`ConfigOptions`] to a given [`ConfigFile`] /// /// # Type Parameters diff --git a/crates/core/src/crypto.rs b/crates/core/src/crypto.rs index 89e88554..37a2a120 100644 --- a/crates/core/src/crypto.rs +++ b/crates/core/src/crypto.rs @@ -1,8 +1,20 @@ -use crate::RusticResult; - pub(crate) mod aespoly1305; pub(crate) mod hasher; +/// [`CryptoErrorKind`] describes the errors that can happen while dealing with Cryptographic functions +#[derive(thiserror::Error, Debug, displaydoc::Display, Copy, Clone)] +#[non_exhaustive] +pub enum CryptoErrorKind { + /// data decryption failed: `{0:?}` + DataDecryptionFailed(aes256ctr_poly1305aes::aead::Error), + /// data encryption failed: `{0:?}` + DataEncryptionFailed(aes256ctr_poly1305aes::aead::Error), + /// crypto key too short + CryptoKeyTooShort, +} + +pub(crate) type CryptoResult = Result; + /// A trait for encrypting and decrypting data. pub trait CryptoKey: Clone + Copy + Sized + Send + Sync + 'static { /// Decrypt the given data. @@ -14,7 +26,7 @@ pub trait CryptoKey: Clone + Copy + Sized + Send + Sync + 'static { /// # Returns /// /// A vector containing the decrypted data. - fn decrypt_data(&self, data: &[u8]) -> RusticResult>; + fn decrypt_data(&self, data: &[u8]) -> CryptoResult>; /// Encrypt the given data. /// @@ -25,5 +37,5 @@ pub trait CryptoKey: Clone + Copy + Sized + Send + Sync + 'static { /// # Returns /// /// A vector containing the encrypted data. - fn encrypt_data(&self, data: &[u8]) -> RusticResult>; + fn encrypt_data(&self, data: &[u8]) -> CryptoResult>; } diff --git a/crates/core/src/index.rs b/crates/core/src/index.rs index 60630a08..42e9b901 100644 --- a/crates/core/src/index.rs +++ b/crates/core/src/index.rs @@ -6,7 +6,7 @@ use derive_more::Constructor; use crate::{ backend::{decrypt::DecryptReadBackend, FileType}, blob::{tree::TreeId, BlobId, BlobType, DataId}, - error::IndexErrorKind, + error::RusticResult, index::binarysorted::{Index, IndexCollector, IndexType}, progress::Progress, repofile::{ @@ -19,6 +19,23 @@ use crate::{ pub(crate) mod binarysorted; pub(crate) mod indexer; +/// [`IndexErrorKind`] describes the errors that can be returned by processing Indizes +#[derive(thiserror::Error, Debug, displaydoc::Display)] +#[non_exhaustive] +pub enum IndexErrorKind { + /// blob not found in index + BlobInIndexNotFound, + /// failed to get a blob from the backend + GettingBlobIndexEntryFromBackendFailed, + /// saving IndexFile failed + SavingIndexFileFailed { + /// the error that occurred + source: CryptBackendErrorKind, + }, +} + +pub(crate) type IndexResult = Result; + /// An entry in the index #[derive(Debug, Clone, Copy, PartialEq, Eq, Constructor)] pub struct IndexEntry { @@ -70,6 +87,7 @@ impl IndexEntry { self.length, self.uncompressed_length, )?; + Ok(data) } diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index ea763bdb..97591168 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -145,7 +145,7 @@ pub use crate::{ repoinfo::{BlobInfo, IndexInfos, PackInfo, RepoFileInfo, RepoFileInfos}, restore::{FileDirStats, RestoreOptions, RestorePlan, RestoreStats}, }, - error::{RusticError, RusticResult}, + error::{ErrorKind, RusticError, RusticResult, Severity}, id::{HexId, Id}, progress::{NoProgress, NoProgressBars, Progress, ProgressBars}, repofile::snapshotfile::{ diff --git a/crates/core/src/repofile/configfile.rs b/crates/core/src/repofile/configfile.rs index 278a95bf..d5aa2d8b 100644 --- a/crates/core/src/repofile/configfile.rs +++ b/crates/core/src/repofile/configfile.rs @@ -6,6 +6,26 @@ use crate::{ impl_repofile, repofile::RepoFile, RusticResult, }; +/// [`ConfigFileErrorKind`] describes the errors that can be returned for `ConfigFile`s +#[derive(thiserror::Error, Debug, displaydoc::Display)] +#[non_exhaustive] +pub enum ConfigFileErrorKind { + /// config version not supported: {version}, compression: {compression:?} + ConfigVersionNotSupported { + /// The version of the config + version: u32, + /// The compression level + compression: Option, + }, + /// Parsing failed for polynomial: {polynomial} : {source} + ParsingFailedForPolynomial { + polynomial: String, + source: ParseIntError, + }, +} + +pub(crate) type ConfigFileResult = Result; + pub(super) mod constants { pub(super) const KB: u32 = 1024; diff --git a/crates/core/src/repofile/keyfile.rs b/crates/core/src/repofile/keyfile.rs index cbd44c2c..037d9fef 100644 --- a/crates/core/src/repofile/keyfile.rs +++ b/crates/core/src/repofile/keyfile.rs @@ -11,6 +11,44 @@ use crate::{ impl_repoid, RusticError, }; +/// [`KeyFileErrorKind`] describes the errors that can be returned for `KeyFile`s +#[derive(thiserror::Error, Debug, displaydoc::Display)] +#[non_exhaustive] +pub enum KeyFileErrorKind { + /// no suitable key found! + NoSuitableKeyFound, + /// listing KeyFiles failed + ListingKeyFilesFailed, + /// couldn't get KeyFile from backend + CouldNotGetKeyFileFromBackend, + /// serde_json couldn't deserialize the data for the key: `{key_id:?}` : `{source}` + DeserializingFromSliceForKeyIdFailed { + /// The id of the key + key_id: KeyId, + /// The error that occurred + source: serde_json::Error, + }, + /// serde_json couldn't serialize the data into a JSON byte vector: `{0:?}` + CouldNotSerializeAsJsonByteVector(serde_json::Error), + /// output length is invalid: `{0:?}` + OutputLengthInvalid(scrypt::errors::InvalidOutputLen), + /// invalid scrypt parameters: `{0:?}` + InvalidSCryptParameters(scrypt::errors::InvalidParams), + /// Could not get key from decrypt data: `{key:?}` : `{source}` + CouldNotGetKeyFromDecryptData { key: Key, source: CryptoErrorKind }, + /// deserializing master key from slice failed: `{source}` + DeserializingMasterKeyFromSliceFailed { source: serde_json::Error }, + /// conversion from {from} to {to} failed for {x} : {source} + ConversionFailed { + from: &'static str, + to: &'static str, + x: u32, + source: std::num::TryFromIntError, + }, +} + +pub(crate) type KeyFileResult = Result; + pub(super) mod constants { /// Returns the number of bits of the given type. pub(super) const fn num_bits() -> usize { diff --git a/crates/core/src/repofile/packfile.rs b/crates/core/src/repofile/packfile.rs index 87a2404d..b3ae670e 100644 --- a/crates/core/src/repofile/packfile.rs +++ b/crates/core/src/repofile/packfile.rs @@ -13,6 +13,30 @@ use crate::{ RusticResult, }; +/// [`PackFileErrorKind`] describes the errors that can be returned for `PackFile`s +#[derive(thiserror::Error, Debug, displaydoc::Display)] +#[non_exhaustive] +pub enum PackFileErrorKind { + /// Failed reading binary representation of the pack header: `{0:?}` + ReadingBinaryRepresentationFailed(binrw::Error), + /// Failed writing binary representation of the pack header: `{0:?}` + WritingBinaryRepresentationFailed(binrw::Error), + /// Read header length is too large! Length: `{size_real}`, file size: `{pack_size}` + HeaderLengthTooLarge { size_real: u32, pack_size: u32 }, + /// Read header length doesn't match header contents! Length: `{size_real}`, computed: `{size_computed}` + HeaderLengthDoesNotMatchHeaderContents { size_real: u32, size_computed: u32 }, + /// pack size computed from header doesn't match real pack isch! Computed: `{size_computed}`, real: `{size_real}` + HeaderPackSizeComputedDoesNotMatchRealPackFile { size_real: u32, size_computed: u32 }, + /// decrypting from binary failed + BinaryDecryptionFailed, + /// Partial read of PackFile failed + PartialReadOfPackfileFailed, + /// writing Bytes failed + WritingBytesFailed, +} + +pub(crate) type PackFileResult = Result; + impl_repoid!(PackId, FileType::Pack); pub(super) mod constants { diff --git a/crates/core/src/repofile/snapshotfile.rs b/crates/core/src/repofile/snapshotfile.rs index a973bfce..17d18883 100644 --- a/crates/core/src/repofile/snapshotfile.rs +++ b/crates/core/src/repofile/snapshotfile.rs @@ -30,6 +30,38 @@ use crate::{ #[cfg(feature = "clap")] use clap::ValueHint; +/// [`SnapshotFileErrorKind`] describes the errors that can be returned for `SnapshotFile`s +#[derive(thiserror::Error, Debug, displaydoc::Display)] +#[non_exhaustive] +pub enum SnapshotFileErrorKind { + /// non-unicode hostname `{0:?}` + NonUnicodeHostname(OsString), + /// non-unicode path `{0:?}` + NonUnicodePath(PathBuf), + /// no snapshots found + NoSnapshotsFound, + /// value `{0:?}` not allowed + ValueNotAllowed(String), + /// datetime out of range: `{0:?}` + OutOfRange(OutOfRangeError), + /// reading the description file failed: `{0:?}` + ReadingDescriptionFailed(std::io::Error), + /// getting the SnapshotFile from the backend failed + GettingSnapshotFileFailed, + /// getting the SnapshotFile by ID failed + GettingSnapshotFileByIdFailed, + /// unpacking SnapshotFile result failed + UnpackingSnapshotFileResultFailed, + /// collecting IDs failed: `{0:?}` + FindingIdsFailed(Vec), + /// removing dots from paths failed: `{0:?}` + RemovingDotsFromPathFailed(std::io::Error), + /// canonicalizing path failed: `{0:?}` + CanonicalizingPathFailed(std::io::Error), +} + +pub(crate) type SnapshotFileResult = Result; + /// Options for creating a new [`SnapshotFile`] structure for a new backup snapshot. /// /// This struct derives [`serde::Deserialize`] allowing to use it in config files. diff --git a/crates/core/src/repository.rs b/crates/core/src/repository.rs index 607a4264..9504dff4 100644 --- a/crates/core/src/repository.rs +++ b/crates/core/src/repository.rs @@ -1,5 +1,5 @@ -mod command_input; -mod warm_up; +pub(crate) mod command_input; +pub(crate) mod warm_up; pub use command_input::CommandInput; @@ -48,7 +48,7 @@ use crate::{ restore::{collect_and_prepare, restore_repository, RestoreOptions, RestorePlan}, }, crypto::aespoly1305::Key, - error::{CommandErrorKind, KeyFileErrorKind, RepositoryErrorKind, RusticErrorKind}, + error::{ErrorKind, RusticResult}, index::{ binarysorted::{IndexCollector, IndexType}, GlobalIndex, IndexEntry, ReadGlobalIndex, ReadIndex, diff --git a/crates/core/src/repository/command_input.rs b/crates/core/src/repository/command_input.rs index 6a258d4d..be9e73b0 100644 --- a/crates/core/src/repository/command_input.rs +++ b/crates/core/src/repository/command_input.rs @@ -8,10 +8,38 @@ use log::{debug, error, trace, warn}; use serde::{Deserialize, Serialize, Serializer}; use serde_with::{serde_as, DisplayFromStr, PickFirst}; -use crate::{ - error::{RepositoryErrorKind, RusticErrorKind}, - RusticError, RusticResult, -}; +use crate::error::RusticResult; + +/// [`CommandInputErrorKind`] describes the errors that can be returned from the CommandInput +#[derive(thiserror::Error, Debug, displaydoc::Display)] +#[non_exhaustive] +pub enum CommandInputErrorKind { + /// Command execution failed: {context}:{what} : {source} + CommandExecutionFailed { + context: String, + what: String, + source: std::io::Error, + }, + /// Command error status: {context}:{what} : {status} + CommandErrorStatus { + context: String, + what: String, + status: ExitStatus, + }, + /// Splitting arguments failed: {arguments} : {source} + SplittingArgumentsFailed { + arguments: String, + source: shell_words::ParseError, + }, + /// Process execution failed: {command:?} : {path:?} : {source} + ProcessExecutionFailed { + command: CommandInput, + path: std::path::PathBuf, + source: std::io::Error, + }, +} + +pub(crate) type CommandInputResult = Result; /// A command to be called which can be given as CLI option as well as in config files /// `CommandInput` implements Serialize/Deserialize as well as FromStr. diff --git a/crates/core/src/repository/warm_up.rs b/crates/core/src/repository/warm_up.rs index 14ea76da..c3805baf 100644 --- a/crates/core/src/repository/warm_up.rs +++ b/crates/core/src/repository/warm_up.rs @@ -13,6 +13,16 @@ use crate::{ CommandInput, }; +/// [`WarmupErrorKind`] describes the errors that can be returned from Warmup +#[derive(thiserror::Error, Debug, displaydoc::Display)] +#[non_exhaustive] +pub enum WarmupErrorKind { + /// Error in warm-up command + General, +} + +pub(crate) type WarmupResult = Result; + pub(super) mod constants { /// The maximum number of reader threads to use for warm-up. pub(super) const MAX_READER_THREADS_NUM: usize = 20; diff --git a/crates/core/src/vfs.rs b/crates/core/src/vfs.rs index 5ae211b9..75130556 100644 --- a/crates/core/src/vfs.rs +++ b/crates/core/src/vfs.rs @@ -28,6 +28,21 @@ use crate::{ RusticResult, }; +/// [`VfsErrorKind`] describes the errors that can be returned from the Virtual File System +#[derive(thiserror::Error, Debug, displaydoc::Display)] +pub enum VfsErrorKind { + /// No directory entries for symlink found: `{0:?}` + NoDirectoryEntriesForSymlinkFound(OsString), + /// Directory exists as non-virtual directory + DirectoryExistsAsNonVirtual, + /// Only normal paths allowed + OnlyNormalPathsAreAllowed, + /// Name `{0:?}`` doesn't exist + NameDoesNotExist(OsString), +} + +pub(crate) type VfsResult = Result; + #[derive(Debug, Clone, Copy)] /// `IdenticalSnapshot` describes how to handle identical snapshots. pub enum IdenticalSnapshot { From ac5a2616b7316715dc157042c70f1fda5ea1f726 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Wed, 23 Oct 2024 18:44:51 +0200 Subject: [PATCH 003/129] Error handling --- crates/backend/src/choose.rs | 6 +- crates/backend/src/local.rs | 6 +- crates/backend/src/util.rs | 3 + crates/core/src/archiver.rs | 20 +- crates/core/src/archiver/file_archiver.rs | 19 +- crates/core/src/archiver/parent.rs | 7 +- crates/core/src/archiver/tree_archiver.rs | 13 +- crates/core/src/backend.rs | 18 +- crates/core/src/backend/cache.rs | 98 +++++- crates/core/src/backend/childstdout.rs | 15 +- crates/core/src/backend/decrypt.rs | 89 ++++-- crates/core/src/backend/dry_run.rs | 14 +- crates/core/src/backend/hotcold.rs | 1 - crates/core/src/backend/ignore.rs | 59 ++-- crates/core/src/backend/local_destination.rs | 167 +++++++--- crates/core/src/backend/node.rs | 74 +++-- crates/core/src/blob/packer.rs | 114 ++++--- crates/core/src/blob/tree.rs | 92 ++++-- crates/core/src/chunker.rs | 3 +- crates/core/src/commands/backup.rs | 16 +- crates/core/src/commands/cat.rs | 5 +- crates/core/src/commands/check.rs | 15 +- crates/core/src/commands/config.rs | 107 ++++--- crates/core/src/commands/dump.rs | 8 +- crates/core/src/commands/forget.rs | 4 +- crates/core/src/commands/init.rs | 4 +- crates/core/src/commands/key.rs | 6 +- crates/core/src/commands/merge.rs | 16 +- crates/core/src/commands/prune.rs | 40 ++- crates/core/src/commands/repair/index.rs | 17 +- crates/core/src/commands/repair/snapshots.rs | 4 +- crates/core/src/commands/repoinfo.rs | 4 +- crates/core/src/commands/restore.rs | 31 +- crates/core/src/crypto/aespoly1305.rs | 8 +- crates/core/src/id.rs | 20 +- crates/core/src/index.rs | 5 +- crates/core/src/repofile/configfile.rs | 24 +- crates/core/src/repofile/keyfile.rs | 91 ++++-- crates/core/src/repofile/packfile.rs | 42 ++- crates/core/src/repofile/snapshotfile.rs | 44 +-- crates/core/src/repository.rs | 319 ++++++++++--------- crates/core/src/repository/command_input.rs | 38 ++- crates/core/src/repository/warm_up.rs | 16 +- crates/core/src/vfs.rs | 51 ++- crates/core/tests/integration.rs | 2 +- 45 files changed, 1113 insertions(+), 642 deletions(-) diff --git a/crates/backend/src/choose.rs b/crates/backend/src/choose.rs index 7455b572..3fed2b2c 100644 --- a/crates/backend/src/choose.rs +++ b/crates/backend/src/choose.rs @@ -7,7 +7,6 @@ use strum_macros::{Display, EnumString}; use rustic_core::{RepositoryBackends, RusticResult, WriteBackend}; use crate::{ - error::BackendAccessErrorKind, local::LocalBackend, util::{location_to_type_and_path, BackendLocation}, }; @@ -119,7 +118,10 @@ impl BackendOptions { .map(|string| { let (be_type, location) = location_to_type_and_path(string)?; be_type.to_backend(location, options.into()).map_err(|err| { - BackendAccessErrorKind::BackendLoadError(be_type.to_string(), err).into() + BackendErrorKind::BackendLoadError { + name: be_type, + source: err, + } }) }) .transpose() diff --git a/crates/backend/src/local.rs b/crates/backend/src/local.rs index 977f5c18..83994881 100644 --- a/crates/backend/src/local.rs +++ b/crates/backend/src/local.rs @@ -266,10 +266,12 @@ impl ReadBackend for LocalBackend { Ok(( e.file_name().to_string_lossy().parse()?, e.metadata() - .map_err(LocalBackendErrorKind::QueryingWalkDirMetadataFailed)? + .map_err(LocalBackendErrorKind::QueryingWalkDirMetadataFailed) + .map_err(|_err| todo!("Error transition"))? .len() .try_into() - .map_err(LocalBackendErrorKind::FromTryIntError)?, + .map_err(LocalBackendErrorKind::FromTryIntError) + .map_err(|_err| todo!("Error transition"))?, )) }) .filter_map(RusticResult::ok); diff --git a/crates/backend/src/util.rs b/crates/backend/src/util.rs index fed4e194..642b722d 100644 --- a/crates/backend/src/util.rs +++ b/crates/backend/src/util.rs @@ -65,6 +65,9 @@ pub fn location_to_type_and_path( SupportedBackend::Local, BackendLocation(raw_location.to_string()), )), + _ => Err(BackendErrorKind::BackendLocationNotConvertible { + location: raw_location.to_string(), + }), } } diff --git a/crates/core/src/archiver.rs b/crates/core/src/archiver.rs index 7c5ed69e..4dedeb47 100644 --- a/crates/core/src/archiver.rs +++ b/crates/core/src/archiver.rs @@ -16,9 +16,13 @@ use crate::{ }, backend::{decrypt::DecryptFullBackend, ReadSource, ReadSourceEntry}, blob::BlobType, - index::{indexer::Indexer, indexer::SharedIndexer, ReadGlobalIndex}, + error::RusticResult, + index::{ + indexer::{Indexer, SharedIndexer}, + ReadGlobalIndex, + }, repofile::{configfile::ConfigFile, snapshotfile::SnapshotFile}, - Progress, RusticResult, + Progress, }; /// [`ArchiverErrorKind`] describes the errors that can be returned from the archiver @@ -221,8 +225,12 @@ impl<'a, BE: DecryptFullBackend, I: ReadGlobalIndex> Archiver<'a, BE, I> { }) .try_for_each(|item| self.tree_archiver.add(item)) }) - .unwrap()?; - src_size_handle.join().unwrap(); + .expect("Scoped Archiver thread should not panic!")?; + + src_size_handle + .join() + .expect("Scoped Size Handler thread should not panic!"); + Ok(()) })?; @@ -233,7 +241,9 @@ impl<'a, BE: DecryptFullBackend, I: ReadGlobalIndex> Archiver<'a, BE, I> { self.indexer.write().unwrap().finalize()?; - summary.finalize(self.snap.time)?; + summary + .finalize(self.snap.time) + .map_err(|_err| todo!("Error transition"))?; self.snap.summary = Some(summary); if !skip_identical_parent || Some(self.snap.tree) != self.parent.tree_id() { diff --git a/crates/core/src/archiver/file_archiver.rs b/crates/core/src/archiver/file_archiver.rs index 74920dda..eb428784 100644 --- a/crates/core/src/archiver/file_archiver.rs +++ b/crates/core/src/archiver/file_archiver.rs @@ -5,6 +5,7 @@ use crate::{ parent::{ItemWithParent, ParentResult}, tree::TreeType, tree_archiver::TreeItem, + ArchiverErrorKind, }, backend::{ decrypt::DecryptWriteBackend, @@ -18,7 +19,7 @@ use crate::{ cdc::rolling_hash::Rabin64, chunker::ChunkIter, crypto::hasher::hash, - error::{ArchiverErrorKind, RusticResult}, + error::RusticResult, index::{indexer::SharedIndexer, ReadGlobalIndex}, progress::Progress, repofile::configfile::ConfigFile, @@ -60,7 +61,7 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> FileArchiver<'a, BE, I> { /// /// [`PackerErrorKind::SendingCrossbeamMessageFailed`]: crate::error::PackerErrorKind::SendingCrossbeamMessageFailed /// [`PackerErrorKind::IntConversionFailed`]: crate::error::PackerErrorKind::IntConversionFailed - pub(crate) fn new( + pub fn new( be: BE, index: &'a I, indexer: SharedIndexer, @@ -75,7 +76,9 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> FileArchiver<'a, BE, I> { config, index.total_size(BlobType::Data), )?; + let rabin = Rabin64::new_with_polynom(6, poly); + Ok(Self { index, data_packer, @@ -118,8 +121,11 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> FileArchiver<'a, BE, I> { (node, size) } else if node.node_type == NodeType::File { let r = open - .ok_or(ArchiverErrorKind::UnpackingTreeTypeOptionalFailed)? - .open()?; + .ok_or(ArchiverErrorKind::UnpackingTreeTypeOptionalFailed) + .map_err(|_err| todo!("Error transition"))? + .open() + .map_err(|_| ArchiverErrorKind::OpeningFileFailed { path: path.clone() }) + .map_err(|_err| todo!("Error transition"))?; self.backup_reader(r, node, p)? } else { (node, 0) @@ -138,12 +144,11 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> FileArchiver<'a, BE, I> { ) -> RusticResult<(Node, u64)> { let chunks: Vec<_> = ChunkIter::new( r, - usize::try_from(node.meta.size) - .map_err(ArchiverErrorKind::ConversionFromU64ToUsizeFailed)?, + usize::try_from(node.meta.size).map_err(|_err| todo!("Error transition"))?, self.rabin.clone(), ) .map(|chunk| { - let chunk = chunk.map_err(ArchiverErrorKind::FromStdIo)?; + let chunk = chunk.map_err(|_err| todo!("Error transition"))?; let id = hash(&chunk); let size = chunk.len() as u64; diff --git a/crates/core/src/archiver/parent.rs b/crates/core/src/archiver/parent.rs index 6db8917f..87f8cf54 100644 --- a/crates/core/src/archiver/parent.rs +++ b/crates/core/src/archiver/parent.rs @@ -6,10 +6,9 @@ use std::{ use log::warn; use crate::{ - archiver::tree::TreeType, + archiver::{tree::TreeType, ArchiverErrorKind, ArchiverResult}, backend::{decrypt::DecryptReadBackend, node::Node}, blob::tree::{Tree, TreeId}, - error::{ArchiverErrorKind, RusticResult}, index::ReadGlobalIndex, }; @@ -221,7 +220,7 @@ impl Parent { /// * [`ArchiverErrorKind::TreeStackEmpty`] - If the tree stack is empty. /// /// [`ArchiverErrorKind::TreeStackEmpty`]: crate::error::ArchiverErrorKind::TreeStackEmpty - fn finish_dir(&mut self) -> RusticResult<()> { + fn finish_dir(&mut self) -> ArchiverResult<()> { let (tree, node_idx) = self .stack .pop() @@ -260,7 +259,7 @@ impl Parent { be: &impl DecryptReadBackend, index: &impl ReadGlobalIndex, item: TreeType, - ) -> RusticResult> { + ) -> ArchiverResult> { let result = match item { TreeType::NewTree((path, node, tree)) => { let parent_result = self diff --git a/crates/core/src/archiver/tree_archiver.rs b/crates/core/src/archiver/tree_archiver.rs index 3e2cc319..58e703f0 100644 --- a/crates/core/src/archiver/tree_archiver.rs +++ b/crates/core/src/archiver/tree_archiver.rs @@ -4,16 +4,16 @@ use bytesize::ByteSize; use log::{debug, trace}; use crate::{ - archiver::{parent::ParentResult, tree::TreeType}, + archiver::{parent::ParentResult, tree::TreeType, ArchiverErrorKind}, backend::{decrypt::DecryptWriteBackend, node::Node}, blob::{ packer::Packer, tree::{Tree, TreeId}, BlobType, }, - error::{ArchiverErrorKind, RusticResult}, index::{indexer::SharedIndexer, ReadGlobalIndex}, repofile::{configfile::ConfigFile, snapshotfile::SnapshotSummary}, + RusticResult, }; pub(crate) type TreeItem = TreeType<(ParentResult<()>, u64), ParentResult>; @@ -76,6 +76,7 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> TreeArchiver<'a, BE, I> { config, index.total_size(BlobType::Tree), )?; + Ok(Self { tree: Tree::new(), stack: Vec::new(), @@ -109,7 +110,8 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> TreeArchiver<'a, BE, I> { let (path, mut node, parent, tree) = self .stack .pop() - .ok_or_else(|| ArchiverErrorKind::TreeStackEmpty)?; + .ok_or_else(|| ArchiverErrorKind::TreeStackEmpty) + .map_err(|_err| todo!("Error transition"))?; // save tree trace!("finishing {path:?}"); @@ -172,7 +174,10 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> TreeArchiver<'a, BE, I> { /// /// [`PackerErrorKind::SendingCrossbeamMessageFailed`]: crate::error::PackerErrorKind::SendingCrossbeamMessageFailed fn backup_tree(&mut self, path: &Path, parent: &ParentResult) -> RusticResult { - let (chunk, id) = self.tree.serialize()?; + let (chunk, id) = self + .tree + .serialize() + .map_err(|_err| todo!("Error transition"))?; let dirsize = chunk.len() as u64; let dirsize_bytes = ByteSize(dirsize).to_string_as(true); diff --git a/crates/core/src/backend.rs b/crates/core/src/backend.rs index 37ba1fa8..1b2ddaff 100644 --- a/crates/core/src/backend.rs +++ b/crates/core/src/backend.rs @@ -10,9 +10,8 @@ pub(crate) mod node; pub(crate) mod stdin; pub(crate) mod warm_up; -use std::{io::Read, ops::Deref, path::PathBuf, sync::Arc}; +use std::{io::Read, num::TryFromIntError, ops::Deref, path::PathBuf, sync::Arc}; -use anyhow::Result; use bytes::Bytes; use enum_map::Enum; use log::trace; @@ -26,7 +25,6 @@ use crate::{ backend::node::{Metadata, Node, NodeType}, error::RusticResult, id::Id, - RusticResult, }; // #[derive(thiserror::Error, Debug, displaydoc::Display)] @@ -273,7 +271,7 @@ pub trait FindInBackend: ReadBackend { NonUnique, } let mut results = vec![MapResult::None; vec.len()]; - for id in self.list(tpe).map_err(RusticErrorKind::Backend)? { + for id in self.list(tpe)? { let id_hex = id.to_hex(); for (i, v) in vec.iter().enumerate() { if id_hex.starts_with(v.as_ref()) { @@ -291,12 +289,13 @@ pub trait FindInBackend: ReadBackend { .enumerate() .map(|(i, id)| match id { MapResult::Some(id) => Ok(id), - MapResult::None => Err(BackendAccessErrorKind::NoSuitableIdFound( + MapResult::None => Err(BackendErrorKind::NoSuitableIdFound( (vec[i]).as_ref().to_string(), - ) - .into()), + )) + .map_err(|_err| todo!("Error transition")), MapResult::NonUnique => { - Err(BackendAccessErrorKind::IdNotUnique((vec[i]).as_ref().to_string()).into()) + Err(BackendErrorKind::IdNotUnique((vec[i]).as_ref().to_string())) + .map_err(|_err| todo!("Error transition")) } }) .collect() @@ -496,7 +495,8 @@ impl ReadSourceEntry { fn from_path(path: PathBuf, open: Option) -> RusticResult { let node = Node::new_node( path.file_name() - .ok_or_else(|| BackendAccessErrorKind::PathNotAllowed(path.clone()))?, + .ok_or_else(|| BackendErrorKind::PathNotAllowed(path.clone())) + .map_err(|_err| todo!("Error transition"))?, NodeType::File, Metadata::default(), ); diff --git a/crates/core/src/backend/cache.rs b/crates/core/src/backend/cache.rs index bc1e1c39..60dd8e48 100644 --- a/crates/core/src/backend/cache.rs +++ b/crates/core/src/backend/cache.rs @@ -260,14 +260,40 @@ impl Cache { let mut path = if let Some(p) = path { p } else { - let mut dir = cache_dir().ok_or_else(|| CacheBackendErrorKind::NoCacheDirectory)?; + let mut dir = cache_dir() + .ok_or_else(|| CacheBackendErrorKind::NoCacheDirectory) + .map_err(|_err| todo!("Error transition"))?; dir.push("rustic"); dir }; - fs::create_dir_all(&path).map_err(CacheBackendErrorKind::FromIoError)?; - cachedir::ensure_tag(&path).map_err(CacheBackendErrorKind::FromIoError)?; + + fs::create_dir_all(&path) + .map_err(|err| CacheBackendErrorKind::Io { + context: "while creating cache directory".into(), + source: err, + tpe: None, + id: id.clone().into_inner(), + }) + .map_err(|_err| todo!("Error transition"))?; + + cachedir::ensure_tag(&path) + .map_err(|err| CacheBackendErrorKind::EnsureTagFailed { + source: err, + path: path.clone(), + }) + .map_err(|_err| todo!("Error transition"))?; + path.push(id.to_hex()); - fs::create_dir_all(&path).map_err(CacheBackendErrorKind::FromIoError)?; + + fs::create_dir_all(&path) + .map_err(|err| CacheBackendErrorKind::Io { + context: "while creating cache directory with id".into(), + source: err, + tpe: None, + id: id.clone().into_inner(), + }) + .map_err(|_err| todo!("Error transition"))?; + Ok(Self { path }) } @@ -400,7 +426,13 @@ impl Cache { Ok(Some(data.into())) } Err(err) if err.kind() == ErrorKind::NotFound => Ok(None), - Err(err) => Err(CacheBackendErrorKind::FromIoError(err).into()), + Err(err) => Err(CacheBackendErrorKind::Io { + context: "while reading full data of file".into(), + source: err, + tpe: Some(tpe.clone()), + id: id.clone(), + }) + .map_err(|_err| todo!("Error transition")), } } @@ -434,14 +466,22 @@ impl Cache { let mut file = match File::open(self.path(tpe, id)) { Ok(file) => file, Err(err) if err.kind() == ErrorKind::NotFound => return Ok(None), - Err(err) => return Err(CacheBackendErrorKind::FromIoError(err).into()), + Err(err) => { + return Err(CacheBackendErrorKind::Io { + context: "while opening file".into(), + source: err, + tpe: Some(tpe.clone()), + id: id.clone(), + }) + .map_err(|_err| todo!("Error transition")) + } }; _ = file .seek(SeekFrom::Start(u64::from(offset))) - .map_err(CacheBackendErrorKind::FromIoError)?; + .map_err(|_err| todo!("Error transition"))?; let mut vec = vec![0; length as usize]; file.read_exact(&mut vec) - .map_err(CacheBackendErrorKind::FromIoError)?; + .map_err(|_err| todo!("Error transition"))?; trace!("cache hit!"); Ok(Some(vec.into())) } @@ -461,16 +501,40 @@ impl Cache { /// [`CacheBackendErrorKind::FromIoError`]: crate::error::CacheBackendErrorKind::FromIoError pub fn write_bytes(&self, tpe: FileType, id: &Id, buf: &Bytes) -> RusticResult<()> { trace!("cache writing tpe: {:?}, id: {}", &tpe, &id); - fs::create_dir_all(self.dir(tpe, id)).map_err(CacheBackendErrorKind::FromIoError)?; + + fs::create_dir_all(self.dir(tpe, id)) + .map_err(|err| CacheBackendErrorKind::Io { + context: "while creating directories".into(), + source: err, + tpe: Some(tpe.clone()), + id: id.clone(), + }) + .map_err(|_err| todo!("Error transition"))?; + let filename = self.path(tpe, id); + let mut file = fs::OpenOptions::new() .create(true) .truncate(true) .write(true) - .open(filename) - .map_err(CacheBackendErrorKind::FromIoError)?; + .open(&filename) + .map_err(|err| CacheBackendErrorKind::Io { + context: "while opening file paths".into(), + source: err, + tpe: Some(tpe.clone()), + id: id.clone(), + }) + .map_err(|_err| todo!("Error transition"))?; + file.write_all(buf) - .map_err(CacheBackendErrorKind::FromIoError)?; + .map_err(|err| CacheBackendErrorKind::Io { + context: "while writing to buffer".into(), + source: err, + tpe: Some(tpe.clone()), + id: id.clone(), + }) + .map_err(|_err| todo!("Error transition"))?; + Ok(()) } @@ -489,7 +553,15 @@ impl Cache { pub fn remove(&self, tpe: FileType, id: &Id) -> RusticResult<()> { trace!("cache writing tpe: {:?}, id: {}", &tpe, &id); let filename = self.path(tpe, id); - fs::remove_file(filename).map_err(CacheBackendErrorKind::FromIoError)?; + fs::remove_file(&filename) + .map_err(|err| CacheBackendErrorKind::Io { + context: format!("while removing file: {filename:?}"), + source: err, + tpe: Some(tpe.clone()), + id: id.clone(), + }) + .map_err(|_err| todo!("Error transition"))?; + Ok(()) } } diff --git a/crates/core/src/backend/childstdout.rs b/crates/core/src/backend/childstdout.rs index 95439213..4debae0b 100644 --- a/crates/core/src/backend/childstdout.rs +++ b/crates/core/src/backend/childstdout.rs @@ -7,8 +7,8 @@ use std::{ use crate::{ backend::{ReadSource, ReadSourceEntry}, - error::{RepositoryErrorKind, RusticResult}, - CommandInput, + error::RusticResult, + repository::command_input::{CommandInput, CommandInputErrorKind}, }; /// The `ChildStdoutSource` is a `ReadSource` when spawning a child process and reading its stdout @@ -35,13 +35,10 @@ impl ChildStdoutSource { .args(cmd.args()) .stdout(Stdio::piped()) .spawn() - .map_err(|err| { - RepositoryErrorKind::CommandExecutionFailed( - "stdin-command".into(), - "call".into(), - err, - ) - .into() + .map_err(|err| CommandInputErrorKind::ProcessExecutionFailed { + command: cmd.clone(), + path: path.clone(), + source: err, }); let process = cmd.on_failure().display_result(process)?; diff --git a/crates/core/src/backend/decrypt.rs b/crates/core/src/backend/decrypt.rs index ed114b5a..00f00f86 100644 --- a/crates/core/src/backend/decrypt.rs +++ b/crates/core/src/backend/decrypt.rs @@ -8,12 +8,12 @@ use zstd::stream::{copy_encode, decode_all, encode_all}; pub use zstd::compression_level_range; use crate::{ - backend::{FileType, ReadBackend, WriteBackend}, + backend::{CryptBackendErrorKind, CryptBackendResult, FileType, ReadBackend, WriteBackend}, crypto::{hasher::hash, CryptoKey}, error::RusticResult, id::Id, repofile::{RepoFile, RepoId}, - Progress, RusticResult, + Progress, }; /// The maximum compression level allowed by zstd @@ -78,9 +78,11 @@ pub trait DecryptReadBackend: ReadBackend + Clone + 'static { let mut data = self.decrypt(data)?; if let Some(length) = uncompressed_length { data = decode_all(&*data) - .map_err(CryptBackendErrorKind::DecodingZstdCompressedDataFailed)?; + .map_err(CryptBackendErrorKind::DecodingZstdCompressedDataFailed) + .map_err(|_err| todo!("Error transition"))?; if data.len() != length.get() as usize { - return Err(CryptBackendErrorKind::LengthOfUncompressedDataDoesNotMatch.into()); + return Err(CryptBackendErrorKind::LengthOfUncompressedDataDoesNotMatch) + .map_err(|_err| todo!("Error transition")); } } Ok(data.into()) @@ -110,9 +112,7 @@ pub trait DecryptReadBackend: ReadBackend + Clone + 'static { uncompressed_length: Option, ) -> RusticResult { self.read_encrypted_from_partial( - &self - .read_partial(tpe, id, cacheable, offset, length) - .map_err(RusticErrorKind::Backend)?, + &self.read_partial(tpe, id, cacheable, offset, length)?, uncompressed_length, ) } @@ -129,7 +129,8 @@ pub trait DecryptReadBackend: ReadBackend + Clone + 'static { fn get_file(&self, id: &Id) -> RusticResult { let data = self.read_encrypted_full(F::TYPE, id)?; Ok(serde_json::from_slice(&data) - .map_err(CryptBackendErrorKind::DeserializingFromBytesOfJsonTextFailed)?) + .map_err(CryptBackendErrorKind::DeserializingFromBytesOfJsonTextFailed) + .map_err(|_err| todo!("Error transition"))?) } /// Streams all files. @@ -144,7 +145,7 @@ pub trait DecryptReadBackend: ReadBackend + Clone + 'static { /// /// If the files could not be read. fn stream_all(&self, p: &impl Progress) -> StreamResult { - let list = self.list(F::TYPE).map_err(RusticErrorKind::Backend)?; + let list = self.list(F::TYPE)?; self.stream_list(&list, p) } @@ -220,10 +221,12 @@ pub trait DecryptWriteBackend: WriteBackend + Clone + 'static { /// /// The hash of the written data. fn hash_write_full_uncompressed(&self, tpe: FileType, data: &[u8]) -> RusticResult { - let data = self.key().encrypt_data(data)?; + let data = self + .key() + .encrypt_data(data) + .map_err(|_err| todo!("Error transition"))?; let id = hash(&data); - self.write_bytes(tpe, &id, false, data.into()) - .map_err(RusticErrorKind::Backend)?; + self.write_bytes(tpe, &id, false, data.into())?; Ok(id) } /// Saves the given file. @@ -243,7 +246,8 @@ pub trait DecryptWriteBackend: WriteBackend + Clone + 'static { /// [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`]: crate::error::CryptBackendErrorKind::SerializingToJsonByteVectorFailed fn save_file(&self, file: &F) -> RusticResult { let data = serde_json::to_vec(file) - .map_err(CryptBackendErrorKind::SerializingToJsonByteVectorFailed)?; + .map_err(CryptBackendErrorKind::SerializingToJsonByteVectorFailed) + .map_err(|_err| todo!("Error transition"))?; self.hash_write_full(F::TYPE, &data) } @@ -264,7 +268,8 @@ pub trait DecryptWriteBackend: WriteBackend + Clone + 'static { /// [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`]: crate::error::CryptBackendErrorKind::SerializingToJsonByteVectorFailed fn save_file_uncompressed(&self, file: &F) -> RusticResult { let data = serde_json::to_vec(file) - .map_err(CryptBackendErrorKind::SerializingToJsonByteVectorFailed)?; + .map_err(CryptBackendErrorKind::SerializingToJsonByteVectorFailed) + .map_err(|_err| todo!("Error transition"))?; self.hash_write_full_uncompressed(F::TYPE, &data) } @@ -380,8 +385,12 @@ impl DecryptBackend { Ok(match decrypted.first() { Some(b'{' | b'[') => decrypted, // not compressed Some(2) => decode_all(&decrypted[1..]) - .map_err(CryptBackendErrorKind::DecodingZstdCompressedDataFailed)?, // 2 indicates compressed data following - _ => return Err(CryptBackendErrorKind::DecryptionNotSupportedForBackend)?, + .map_err(CryptBackendErrorKind::DecodingZstdCompressedDataFailed) + .map_err(|_err| todo!("Error transition"))?, // 2 indicates compressed data following + _ => { + return Err(CryptBackendErrorKind::DecryptionNotSupportedForBackend) + .map_err(|_err| todo!("Error transition"))? + } }) } @@ -392,9 +401,14 @@ impl DecryptBackend { let mut out = vec![2_u8]; copy_encode(data, &mut out, level) .map_err(CryptBackendErrorKind::CopyEncodingDataFailed)?; - self.key().encrypt_data(&out)? + self.key() + .encrypt_data(&out) + .map_err(|_err| todo!("Error transition"))? } - None => self.key().encrypt_data(data)?, + None => self + .key() + .encrypt_data(data) + .map_err(|_err| todo!("Error transition"))?, }; Ok(data_encrypted) } @@ -403,23 +417,33 @@ impl DecryptBackend { if self.extra_verify { let check_data = self.decrypt_file(data_encrypted)?; if data != check_data { - return Err(CryptBackendErrorKind::ExtraVerificationFailed.into()); + return Err(CryptBackendErrorKind::ExtraVerificationFailed) + .map_err(|_err| todo!("Error transition")); } } Ok(()) } /// encrypt and potentially compress some data - fn encrypt_data(&self, data: &[u8]) -> RusticResult<(Vec, u32, Option)> { + fn encrypt_data(&self, data: &[u8]) -> CryptBackendResult<(Vec, u32, Option)> { let data_len: u32 = data .len() .try_into() .map_err(CryptBackendErrorKind::IntConversionFailed)?; let (data_encrypted, uncompressed_length) = match self.zstd { - None => (self.key.encrypt_data(data)?, None), + None => ( + self.key + .encrypt_data(data) + .map_err(|_err| todo!("Error transition"))?, + None, + ), // compress if requested Some(level) => ( - self.key.encrypt_data(&encode_all(data, level)?)?, + self.key + .encrypt_data( + &encode_all(data, level).map_err(|_err| todo!("Error transition"))?, + ) + .map_err(|_err| todo!("Error transition"))?, NonZeroU32::new(data_len), ), }; @@ -436,7 +460,8 @@ impl DecryptBackend { let data_check = self.read_encrypted_from_partial(data_encrypted, uncompressed_length)?; if data != data_check { - return Err(CryptBackendErrorKind::ExtraVerificationFailed.into()); + return Err(CryptBackendErrorKind::ExtraVerificationFailed) + .map_err(|_err| todo!("Error transition")); } } Ok(()) @@ -469,16 +494,19 @@ impl DecryptWriteBackend for DecryptBackend { /// /// [`CryptBackendErrorKind::CopyEncodingDataFailed`]: crate::error::CryptBackendErrorKind::CopyEncodingDataFailed fn hash_write_full(&self, tpe: FileType, data: &[u8]) -> RusticResult { - let data_encrypted = self.encrypt_file(data)?; + let data_encrypted = self + .encrypt_file(data) + .map_err(|_err| todo!("Error transition"))?; self.very_file(&data_encrypted, data)?; let id = hash(&data_encrypted); - self.write_bytes(tpe, &id, false, data_encrypted.into()) - .map_err(RusticErrorKind::Backend)?; + self.write_bytes(tpe, &id, false, data_encrypted.into())?; Ok(id) } fn process_data(&self, data: &[u8]) -> RusticResult<(Vec, u32, Option)> { - let (data_encrypted, data_len, uncompressed_length) = self.encrypt_data(data)?; + let (data_encrypted, data_len, uncompressed_length) = self + .encrypt_data(data) + .map_err(|_err| todo!("Error transition"))?; self.very_data(&data_encrypted, uncompressed_length, data)?; Ok((data_encrypted, data_len, uncompressed_length)) } @@ -513,7 +541,9 @@ impl DecryptReadBackend for DecryptBackend { /// /// A vector containing the decrypted data. fn decrypt(&self, data: &[u8]) -> RusticResult> { - self.key.decrypt_data(data) + self.key + .decrypt_data(data) + .map_err(|_err| todo!("Error transition")) } /// Reads encrypted data from the backend. @@ -531,8 +561,7 @@ impl DecryptReadBackend for DecryptBackend { /// [`CryptBackendErrorKind::DecryptionNotSupportedForBackend`]: crate::error::CryptBackendErrorKind::DecryptionNotSupportedForBackend /// [`CryptBackendErrorKind::DecodingZstdCompressedDataFailed`]: crate::error::CryptBackendErrorKind::DecodingZstdCompressedDataFailed fn read_encrypted_full(&self, tpe: FileType, id: &Id) -> RusticResult { - self.decrypt_file(&self.read_full(tpe, id).map_err(RusticErrorKind::Backend)?) - .map(Into::into) + self.decrypt_file(&self.read_full(tpe, id)?).map(Into::into) } } diff --git a/crates/core/src/backend/dry_run.rs b/crates/core/src/backend/dry_run.rs index 9642b1c7..6dca6fd1 100644 --- a/crates/core/src/backend/dry_run.rs +++ b/crates/core/src/backend/dry_run.rs @@ -1,11 +1,10 @@ -use anyhow::Result; use bytes::Bytes; use zstd::decode_all; use crate::{ backend::{ decrypt::{DecryptFullBackend, DecryptReadBackend, DecryptWriteBackend}, - FileType, ReadBackend, WriteBackend, + CryptBackendErrorKind, FileType, ReadBackend, WriteBackend, }, error::RusticResult, id::Id, @@ -60,13 +59,16 @@ impl DecryptReadBackend for DryRunBackend { /// [`CryptBackendErrorKind::DecryptionNotSupportedForBackend`]: crate::error::CryptBackendErrorKind::DecryptionNotSupportedForBackend /// [`CryptBackendErrorKind::DecodingZstdCompressedDataFailed`]: crate::error::CryptBackendErrorKind::DecodingZstdCompressedDataFailed fn read_encrypted_full(&self, tpe: FileType, id: &Id) -> RusticResult { - let decrypted = - self.decrypt(&self.read_full(tpe, id).map_err(RusticErrorKind::Backend)?)?; + let decrypted = self.decrypt(&self.read_full(tpe, id)?)?; Ok(match decrypted.first() { Some(b'{' | b'[') => decrypted, // not compressed Some(2) => decode_all(&decrypted[1..]) - .map_err(CryptBackendErrorKind::DecodingZstdCompressedDataFailed)?, // 2 indicates compressed data following - _ => return Err(CryptBackendErrorKind::DecryptionNotSupportedForBackend.into()), + .map_err(CryptBackendErrorKind::DecodingZstdCompressedDataFailed) + .map_err(|_err| todo!("Error transition"))?, // 2 indicates compressed data following + _ => { + return Err(CryptBackendErrorKind::DecryptionNotSupportedForBackend) + .map_err(|_err| todo!("Error transition")) + } } .into()) } diff --git a/crates/core/src/backend/hotcold.rs b/crates/core/src/backend/hotcold.rs index 3885d85e..e5306e2f 100644 --- a/crates/core/src/backend/hotcold.rs +++ b/crates/core/src/backend/hotcold.rs @@ -1,6 +1,5 @@ use std::sync::Arc; -use anyhow::Result; use bytes::Bytes; use crate::{ diff --git a/crates/core/src/backend/ignore.rs b/crates/core/src/backend/ignore.rs index b69e8d23..8ebc3a38 100644 --- a/crates/core/src/backend/ignore.rs +++ b/crates/core/src/backend/ignore.rs @@ -6,8 +6,6 @@ use std::{ path::{Path, PathBuf}, }; -use serde_with::{serde_as, DisplayFromStr}; - use bytesize::ByteSize; #[cfg(not(windows))] use cached::proc_macro::cached; @@ -19,6 +17,7 @@ use ignore::{overrides::OverrideBuilder, DirEntry, Walk, WalkBuilder}; use log::warn; #[cfg(not(windows))] use nix::unistd::{Gid, Group, Uid, User}; +use serde_with::{serde_as, DisplayFromStr}; #[cfg(not(windows))] use crate::backend::node::ExtendedAttribute; @@ -198,7 +197,7 @@ impl LocalSource { for g in &filter_opts.globs { _ = override_builder .add(g) - .map_err(IgnoreErrorKind::GenericError)?; + .map_err(|_err| todo!("Error transition"))?; } for file in &filter_opts.glob_files { @@ -206,22 +205,23 @@ impl LocalSource { .map_err(|err| IgnoreErrorKind::ErrorGlob { file: file.into(), source: err, - })? + }) + .map_err(|_err| todo!("Error transition"))? .lines() { _ = override_builder .add(line) - .map_err(IgnoreErrorKind::GenericError)?; + .map_err(|_err| todo!("Error transition"))?; } } _ = override_builder .case_insensitive(true) - .map_err(IgnoreErrorKind::GenericError)?; + .map_err(|_err| todo!("Error transition"))?; for g in &filter_opts.iglobs { _ = override_builder .add(g) - .map_err(IgnoreErrorKind::GenericError)?; + .map_err(|_err| todo!("Error transition"))?; } for file in &filter_opts.iglob_files { @@ -229,12 +229,13 @@ impl LocalSource { .map_err(|err| IgnoreErrorKind::ErrorGlob { file: file.into(), source: err, - })? + }) + .map_err(|_err| todo!("Error transition"))? .lines() { _ = override_builder .add(line) - .map_err(IgnoreErrorKind::GenericError)?; + .map_err(|_err| todo!("Error transition"))?; } } @@ -254,7 +255,7 @@ impl LocalSource { .overrides( override_builder .build() - .map_err(IgnoreErrorKind::GenericError)?, + .map_err(|_err| todo!("Error transition"))?, ); let exclude_if_present = filter_opts.exclude_if_present.clone(); @@ -298,13 +299,12 @@ impl ReadSourceOpen for OpenFile { /// [`IgnoreErrorKind::UnableToOpenFile`]: crate::error::IgnoreErrorKind::UnableToOpenFile fn open(self) -> RusticResult { let path = self.0; - File::open(&path).map_err(|err| { - IgnoreErrorKind::UnableToOpenFile { + File::open(&path) + .map_err(|err| IgnoreErrorKind::UnableToOpenFile { file: path, source: err, - } - .into() - }) + }) + .map_err(|_err| todo!("Error transition")) } } @@ -368,11 +368,11 @@ impl Iterator for LocalSourceWalker { } .map(|e| { map_entry( - e.map_err(IgnoreErrorKind::GenericError)?, + e.map_err(|_err| todo!("Error transition"))?, self.save_opts.with_atime, self.save_opts.ignore_devid, ) - .map_err(Into::into) + .map_err(|_err| todo!("Error transition")) }) } } @@ -398,9 +398,9 @@ fn map_entry( entry: DirEntry, with_atime: bool, _ignore_devid: bool, -) -> RusticResult> { +) -> IgnoreResult> { let name = entry.file_name(); - let m = entry.metadata().map_err(IgnoreErrorKind::GenericError)?; + let m = entry.metadata().map_err(|_err| todo!("Error transition"))?; // TODO: Set them to suitable values let uid = None; @@ -511,7 +511,7 @@ fn get_group_by_gid(gid: u32) -> Option { } #[cfg(all(not(windows), target_os = "openbsd"))] -fn list_extended_attributes(path: &Path) -> RusticResult> { +fn list_extended_attributes(path: &Path) -> IgnoreResult> { Ok(vec![]) } @@ -525,7 +525,7 @@ fn list_extended_attributes(path: &Path) -> RusticResult> /// /// * [`IgnoreErrorKind::ErrorXattr`] - if Xattr couldn't be listed or couldn't be read #[cfg(all(not(windows), not(target_os = "openbsd")))] -fn list_extended_attributes(path: &Path) -> RusticResult> { +fn list_extended_attributes(path: &Path) -> IgnoreResult> { xattr::list(path) .map_err(|err| IgnoreErrorKind::ErrorXattr { path: path.to_path_buf(), @@ -540,7 +540,7 @@ fn list_extended_attributes(path: &Path) -> RusticResult> })?, }) }) - .collect::>>() + .collect::>>() } /// Maps a [`DirEntry`] to a [`ReadSourceEntry`]. @@ -565,9 +565,14 @@ fn map_entry( entry: DirEntry, with_atime: bool, ignore_devid: bool, -) -> RusticResult> { +) -> IgnoreResult> { let name = entry.file_name(); - let m = entry.metadata().map_err(IgnoreErrorKind::GenericError)?; + let m = entry + .metadata() + .map_err(|err| IgnoreErrorKind::AcquiringMetadataFailed { + name: name.to_string_lossy().to_string(), + source: err, + })?; let uid = m.uid(); let gid = m.gid(); @@ -591,7 +596,11 @@ fn map_entry( m.ctime(), m.ctime_nsec() .try_into() - .map_err(IgnoreErrorKind::FromTryFromIntError)?, + .map_err(|err| IgnoreErrorKind::CtimeConversionFailed { + ctime: m.ctime(), + ctime_nsec: m.ctime_nsec(), + source: err, + })?, ) .single() .map(|dt| dt.with_timezone(&Local)); diff --git a/crates/core/src/backend/local_destination.rs b/crates/core/src/backend/local_destination.rs index 99fe5a14..fa1ea636 100644 --- a/crates/core/src/backend/local_destination.rs +++ b/crates/core/src/backend/local_destination.rs @@ -4,6 +4,7 @@ use std::os::unix::fs::{symlink, PermissionsExt}; use std::{ fs::{self, File, OpenOptions}, io::{Read, Seek, SeekFrom, Write}, + num::TryFromIntError, path::{Path, PathBuf}, }; @@ -14,6 +15,8 @@ use filetime::{set_symlink_file_times, FileTime}; #[cfg(not(windows))] use log::warn; #[cfg(not(windows))] +use nix::errno::Errno; +#[cfg(not(windows))] use nix::sys::stat::{mknod, Mode, SFlag}; #[cfg(not(windows))] use nix::{ @@ -151,11 +154,13 @@ impl LocalDestination { if is_file { if let Some(path) = path.parent() { fs::create_dir_all(path) - .map_err(LocalDestinationErrorKind::DirectoryCreationFailed)?; + .map_err(LocalDestinationErrorKind::DirectoryCreationFailed) + .map_err(|_err| todo!("Error transition"))?; } } else { fs::create_dir_all(&path) - .map_err(LocalDestinationErrorKind::DirectoryCreationFailed)?; + .map_err(LocalDestinationErrorKind::DirectoryCreationFailed) + .map_err(|_err| todo!("Error transition"))?; } } @@ -199,7 +204,7 @@ impl LocalDestination { /// This will remove the directory recursively. /// /// [`LocalDestinationErrorKind::DirectoryRemovalFailed`]: crate::error::LocalDestinationErrorKind::DirectoryRemovalFailed - pub fn remove_dir(&self, dirname: impl AsRef) -> RusticResult<()> { + pub(crate) fn remove_dir(&self, dirname: impl AsRef) -> LocalDestinationResult<()> { Ok(fs::remove_dir_all(dirname) .map_err(LocalDestinationErrorKind::DirectoryRemovalFailed)?) } @@ -222,7 +227,7 @@ impl LocalDestination { /// * If the file is a directory or device, this will fail. /// /// [`LocalDestinationErrorKind::FileRemovalFailed`]: crate::error::LocalDestinationErrorKind::FileRemovalFailed - pub fn remove_file(&self, filename: impl AsRef) -> RusticResult<()> { + pub(crate) fn remove_file(&self, filename: impl AsRef) -> LocalDestinationResult<()> { Ok(fs::remove_file(filename).map_err(LocalDestinationErrorKind::FileRemovalFailed)?) } @@ -241,7 +246,7 @@ impl LocalDestination { /// This will create the directory structure recursively. /// /// [`LocalDestinationErrorKind::DirectoryCreationFailed`]: crate::error::LocalDestinationErrorKind::DirectoryCreationFailed - pub fn create_dir(&self, item: impl AsRef) -> RusticResult<()> { + pub(crate) fn create_dir(&self, item: impl AsRef) -> LocalDestinationResult<()> { let dirname = self.path.join(item); fs::create_dir_all(dirname).map_err(LocalDestinationErrorKind::DirectoryCreationFailed)?; Ok(()) @@ -259,7 +264,11 @@ impl LocalDestination { /// * [`LocalDestinationErrorKind::SettingTimeMetadataFailed`] - If the times could not be set /// /// [`LocalDestinationErrorKind::SettingTimeMetadataFailed`]: crate::error::LocalDestinationErrorKind::SettingTimeMetadataFailed - pub fn set_times(&self, item: impl AsRef, meta: &Metadata) -> RusticResult<()> { + pub(crate) fn set_times( + &self, + item: impl AsRef, + meta: &Metadata, + ) -> LocalDestinationResult<()> { let filename = self.path(item); if let Some(mtime) = meta.mtime { let atime = meta.atime.unwrap_or(mtime); @@ -286,7 +295,11 @@ impl LocalDestination { /// # Errors /// /// If the user/group could not be set. - pub fn set_user_group(&self, _item: impl AsRef, _meta: &Metadata) -> RusticResult<()> { + pub(crate) fn set_user_group( + &self, + _item: impl AsRef, + _meta: &Metadata, + ) -> LocalDestinationResult<()> { // https://learn.microsoft.com/en-us/windows/win32/fileio/file-security-and-access-rights // https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/Security/struct.SECURITY_ATTRIBUTES.html // https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/Storage/FileSystem/struct.CREATEFILE2_EXTENDED_PARAMETERS.html#structfield.lpSecurityAttributes @@ -307,7 +320,11 @@ impl LocalDestination { /// /// [`LocalDestinationErrorKind::FromErrnoError`]: crate::error::LocalDestinationErrorKind::FromErrnoError #[allow(clippy::similar_names)] - pub fn set_user_group(&self, item: impl AsRef, meta: &Metadata) -> RusticResult<()> { + pub(crate) fn set_user_group( + &self, + item: impl AsRef, + meta: &Metadata, + ) -> LocalDestinationResult<()> { let filename = self.path(item); let user = meta.user.clone().and_then(uid_from_name); @@ -335,7 +352,11 @@ impl LocalDestination { /// # Errors /// /// If the uid/gid could not be set. - pub fn set_uid_gid(&self, _item: impl AsRef, _meta: &Metadata) -> RusticResult<()> { + pub(crate) fn set_uid_gid( + &self, + _item: impl AsRef, + _meta: &Metadata, + ) -> LocalDestinationResult<()> { Ok(()) } @@ -353,7 +374,11 @@ impl LocalDestination { /// /// [`LocalDestinationErrorKind::FromErrnoError`]: crate::error::LocalDestinationErrorKind::FromErrnoError #[allow(clippy::similar_names)] - pub fn set_uid_gid(&self, item: impl AsRef, meta: &Metadata) -> RusticResult<()> { + pub(crate) fn set_uid_gid( + &self, + item: impl AsRef, + meta: &Metadata, + ) -> LocalDestinationResult<()> { let filename = self.path(item); let uid = meta.uid.map(Uid::from_raw); @@ -376,7 +401,11 @@ impl LocalDestination { /// # Errors /// /// If the permissions could not be set. - pub fn set_permission(&self, _item: impl AsRef, _node: &Node) -> RusticResult<()> { + pub(crate) fn set_permission( + &self, + _item: impl AsRef, + _node: &Node, + ) -> LocalDestinationResult<()> { Ok(()) } @@ -394,7 +423,11 @@ impl LocalDestination { /// /// [`LocalDestinationErrorKind::SettingFilePermissionsFailed`]: crate::error::LocalDestinationErrorKind::SettingFilePermissionsFailed #[allow(clippy::similar_names)] - pub fn set_permission(&self, item: impl AsRef, node: &Node) -> RusticResult<()> { + pub(crate) fn set_permission( + &self, + item: impl AsRef, + node: &Node, + ) -> LocalDestinationResult<()> { if node.is_symlink() { return Ok(()); } @@ -422,11 +455,11 @@ impl LocalDestination { /// # Errors /// /// If the extended attributes could not be set. - pub fn set_extended_attributes( + pub(crate) fn set_extended_attributes( &self, _item: impl AsRef, _extended_attributes: &[ExtendedAttribute], - ) -> RusticResult<()> { + ) -> LocalDestinationResult<()> { Ok(()) } @@ -455,11 +488,11 @@ impl LocalDestination { /// # Panics /// /// If the extended attributes could not be set. - pub fn set_extended_attributes( + pub(crate) fn set_extended_attributes( &self, item: impl AsRef, extended_attributes: &[ExtendedAttribute], - ) -> RusticResult<()> { + ) -> LocalDestinationResult<()> { let filename = self.path(item); let mut done = vec![false; extended_attributes.len()]; @@ -536,7 +569,11 @@ impl LocalDestination { /// [`LocalDestinationErrorKind::DirectoryCreationFailed`]: crate::error::LocalDestinationErrorKind::DirectoryCreationFailed /// [`LocalDestinationErrorKind::OpeningFileFailed`]: crate::error::LocalDestinationErrorKind::OpeningFileFailed /// [`LocalDestinationErrorKind::SettingFileLengthFailed`]: crate::error::LocalDestinationErrorKind::SettingFileLengthFailed - pub fn set_length(&self, item: impl AsRef, size: u64) -> RusticResult<()> { + pub(crate) fn set_length( + &self, + item: impl AsRef, + size: u64, + ) -> LocalDestinationResult<()> { let filename = self.path(item); let dir = filename .parent() @@ -570,7 +607,11 @@ impl LocalDestination { /// # Returns /// /// Ok if the special file was created. - pub fn create_special(&self, _item: impl AsRef, _node: &Node) -> RusticResult<()> { + pub(crate) fn create_special( + &self, + _item: impl AsRef, + _node: &Node, + ) -> LocalDestinationResult<()> { Ok(()) } @@ -585,13 +626,17 @@ impl LocalDestination { /// # Errors /// /// * [`LocalDestinationErrorKind::SymlinkingFailed`] - If the symlink could not be created. - /// * [`LocalDestinationErrorKind::FromTryIntError`] - If the device could not be converted to the correct type. + /// * [`LocalDestinationErrorKind::DeviceIdConversionFailed`] - If the device could not be converted to the correct type. /// * [`LocalDestinationErrorKind::FromErrnoError`] - If the device could not be created. /// - /// [`LocalDestinationErrorKind::SymlinkingFailed`]: crate::error::LocalDestinationErrorKind::SymlinkingFailed - /// [`LocalDestinationErrorKind::FromTryIntError`]: crate::error::LocalDestinationErrorKind::FromTryIntError - /// [`LocalDestinationErrorKind::FromErrnoError`]: crate::error::LocalDestinationErrorKind::FromErrnoError - pub fn create_special(&self, item: impl AsRef, node: &Node) -> RusticResult<()> { + /// [`LocalDestinationErrorKind::SymlinkingFailed`]: LocalDestinationErrorKind::SymlinkingFailed + /// [`LocalDestinationErrorKind::DeviceIdConversionFailed`]: LocalDestinationErrorKind::DeviceIdConversionFailed + /// [`LocalDestinationErrorKind::FromErrnoError`]: LocalDestinationErrorKind::FromErrnoError + pub(crate) fn create_special( + &self, + item: impl AsRef, + node: &Node, + ) -> LocalDestinationResult<()> { let filename = self.path(item); match &node.node_type { @@ -613,11 +658,21 @@ impl LocalDestination { )))] let device = *device; #[cfg(any(target_os = "macos", target_os = "openbsd"))] - let device = - i32::try_from(*device).map_err(LocalDestinationErrorKind::FromTryIntError)?; + let device = i32::try_from(*device).map_err(|err| { + LocalDestinationErrorKind::DeviceIdConversionFailed { + target: "i32".to_string(), + device: *device, + source: err, + } + })?; #[cfg(target_os = "freebsd")] - let device = - u32::try_from(*device).map_err(LocalDestinationErrorKind::FromTryIntError)?; + let device = u32::try_from(*device).map_err(|err| { + LocalDestinationErrorKind::DeviceIdConversionFailed { + target: "u32".to_string(), + device: *device, + source: err, + } + })?; mknod(&filename, SFlag::S_IFBLK, Mode::empty(), device) .map_err(LocalDestinationErrorKind::FromErrnoError)?; } @@ -629,11 +684,21 @@ impl LocalDestination { )))] let device = *device; #[cfg(any(target_os = "macos", target_os = "openbsd"))] - let device = - i32::try_from(*device).map_err(LocalDestinationErrorKind::FromTryIntError)?; + let device = i32::try_from(*device).map_err(|err| { + LocalDestinationErrorKind::DeviceIdConversionFailed { + target: "i32".to_string(), + device: *device, + source: err, + } + })?; #[cfg(target_os = "freebsd")] - let device = - u32::try_from(*device).map_err(LocalDestinationErrorKind::FromTryIntError)?; + let device = u32::try_from(*device).map_err(|err| { + LocalDestinationErrorKind::DeviceIdConversionFailed { + target: "u32".to_string(), + device: *device, + source: err, + } + })?; mknod(&filename, SFlag::S_IFCHR, Mode::empty(), device) .map_err(LocalDestinationErrorKind::FromErrnoError)?; } @@ -661,15 +726,20 @@ impl LocalDestination { /// # Errors /// /// * [`LocalDestinationErrorKind::OpeningFileFailed`] - If the file could not be opened. - /// * [`LocalDestinationErrorKind::CouldNotSeekToPositionInFile`] - If the file could not be seeked to the given position. - /// * [`LocalDestinationErrorKind::FromTryIntError`] - If the length of the file could not be converted to u32. + /// * [`LocalDestinationErrorKind::CouldNotSeekToPositionInFile`] - If the file could not be sought to the given position. + /// * [`LocalDestinationErrorKind::LengthConversionFailed`] - If the length of the file could not be converted to u32. /// * [`LocalDestinationErrorKind::ReadingExactLengthOfFileFailed`] - If the length of the file could not be read. /// - /// [`LocalDestinationErrorKind::OpeningFileFailed`]: crate::error::LocalDestinationErrorKind::OpeningFileFailed - /// [`LocalDestinationErrorKind::CouldNotSeekToPositionInFile`]: crate::error::LocalDestinationErrorKind::CouldNotSeekToPositionInFile - /// [`LocalDestinationErrorKind::FromTryIntError`]: crate::error::LocalDestinationErrorKind::FromTryIntError - /// [`LocalDestinationErrorKind::ReadingExactLengthOfFileFailed`]: crate::error::LocalDestinationErrorKind::ReadingExactLengthOfFileFailed - pub fn read_at(&self, item: impl AsRef, offset: u64, length: u64) -> RusticResult { + /// [`LocalDestinationErrorKind::OpeningFileFailed`]: LocalDestinationErrorKind::OpeningFileFailed + /// [`LocalDestinationErrorKind::CouldNotSeekToPositionInFile`]: LocalDestinationErrorKind::CouldNotSeekToPositionInFile + /// [`LocalDestinationErrorKind::LengthConversionFailed`]: LocalDestinationErrorKind::LengthConversionFailed + /// [`LocalDestinationErrorKind::ReadingExactLengthOfFileFailed`]: LocalDestinationErrorKind::ReadingExactLengthOfFileFailed + pub(crate) fn read_at( + &self, + item: impl AsRef, + offset: u64, + length: u64, + ) -> LocalDestinationResult { let filename = self.path(item); let mut file = File::open(filename).map_err(LocalDestinationErrorKind::OpeningFileFailed)?; @@ -678,9 +748,13 @@ impl LocalDestination { .map_err(LocalDestinationErrorKind::CouldNotSeekToPositionInFile)?; let mut vec = vec![ 0; - length - .try_into() - .map_err(LocalDestinationErrorKind::FromTryIntError)? + length.try_into().map_err(|err| { + LocalDestinationErrorKind::LengthConversionFailed { + target: "u8".to_string(), + length, + source: err, + } + })? ]; file.read_exact(&mut vec) .map_err(LocalDestinationErrorKind::ReadingExactLengthOfFileFailed)?; @@ -698,7 +772,7 @@ impl LocalDestination { /// /// If a file exists and size matches, this returns a `File` open for reading. /// In all other cases, returns `None` - pub fn get_matching_file(&self, item: impl AsRef, size: u64) -> Option { + pub(crate) fn get_matching_file(&self, item: impl AsRef, size: u64) -> Option { let filename = self.path(item); fs::symlink_metadata(&filename).map_or_else( |_| None, @@ -723,7 +797,7 @@ impl LocalDestination { /// # Errors /// /// * [`LocalDestinationErrorKind::OpeningFileFailed`] - If the file could not be opened. - /// * [`LocalDestinationErrorKind::CouldNotSeekToPositionInFile`] - If the file could not be seeked to the given position. + /// * [`LocalDestinationErrorKind::CouldNotSeekToPositionInFile`] - If the file could not be sought to the given position. /// * [`LocalDestinationErrorKind::CouldNotWriteToBuffer`] - If the bytes could not be written to the file. /// /// # Notes @@ -733,7 +807,12 @@ impl LocalDestination { /// [`LocalDestinationErrorKind::OpeningFileFailed`]: crate::error::LocalDestinationErrorKind::OpeningFileFailed /// [`LocalDestinationErrorKind::CouldNotSeekToPositionInFile`]: crate::error::LocalDestinationErrorKind::CouldNotSeekToPositionInFile /// [`LocalDestinationErrorKind::CouldNotWriteToBuffer`]: crate::error::LocalDestinationErrorKind::CouldNotWriteToBuffer - pub fn write_at(&self, item: impl AsRef, offset: u64, data: &[u8]) -> RusticResult<()> { + pub(crate) fn write_at( + &self, + item: impl AsRef, + offset: u64, + data: &[u8], + ) -> LocalDestinationResult<()> { let filename = self.path(item); let mut file = OpenOptions::new() .create(true) diff --git a/crates/core/src/backend/node.rs b/crates/core/src/backend/node.rs index 2b3e7304..3ff4522e 100644 --- a/crates/core/src/backend/node.rs +++ b/crates/core/src/backend/node.rs @@ -11,11 +11,10 @@ use std::fmt::Write; #[cfg(not(windows))] use std::os::unix::ffi::OsStrExt; -#[cfg(not(windows))] -use crate::RusticResult; - use chrono::{DateTime, Local}; use derive_more::Constructor; +#[cfg(not(windows))] +use displaydoc::Display; use serde_aux::prelude::*; use serde_derive::{Deserialize, Serialize}; use serde_with::{ @@ -23,9 +22,8 @@ use serde_with::{ formats::Padded, serde_as, skip_serializing_none, DefaultOnNull, }; - #[cfg(not(windows))] -use crate::error::NodeErrorKind; +use thiserror::Error; use crate::blob::{tree::TreeId, DataId}; @@ -459,7 +457,7 @@ fn escape_filename(name: &OsStr) -> String { /// /// * `s` - The escaped filename // inspired by the enquote crate -fn unescape_filename(s: &str) -> RusticResult { +fn unescape_filename(s: &str) -> NodeResult { let mut chars = s.chars(); let mut u = Vec::new(); loop { @@ -468,7 +466,12 @@ fn unescape_filename(s: &str) -> RusticResult { Some(c) => { if c == '\\' { match chars.next() { - None => return Err(NodeErrorKind::UnexpectedEOF.into()), + None => { + return Err(NodeErrorKind::UnexpectedEOF { + file_name: s.to_string(), + chars, + }) + } Some(c) => match c { '\\' => u.push(b'\\'), '"' => u.push(b'"'), @@ -484,31 +487,62 @@ fn unescape_filename(s: &str) -> RusticResult { // hex 'x' => { let hex = take(&mut chars, 2); - u.push( - u8::from_str_radix(&hex, 16) - .map_err(NodeErrorKind::FromParseIntError)?, - ); + u.push(u8::from_str_radix(&hex, 16).map_err(|err| { + NodeErrorKind::ParsingHexFailed { + file_name: s.to_string(), + hex: hex.to_string(), + chars, + source: err, + } + })?); } // unicode 'u' => { - let n = u32::from_str_radix(&take(&mut chars, 4), 16) - .map_err(NodeErrorKind::FromParseIntError)?; - let c = - std::char::from_u32(n).ok_or(NodeErrorKind::InvalidUnicode)?; + let n = u32::from_str_radix(&take(&mut chars, 4), 16).map_err( + |err| NodeErrorKind::ParsingUnicodeFailed { + file_name: s.to_string(), + target: "u32".to_string(), + chars, + source: err, + }, + )?; + let c = std::char::from_u32(n).ok_or( + NodeErrorKind::InvalidUnicode { + file_name: s.to_string(), + unicode: n, + chars, + }, + )?; let mut bytes = vec![0u8; c.len_utf8()]; _ = c.encode_utf8(&mut bytes); u.extend_from_slice(&bytes); } 'U' => { - let n = u32::from_str_radix(&take(&mut chars, 8), 16) - .map_err(NodeErrorKind::FromParseIntError)?; - let c = - std::char::from_u32(n).ok_or(NodeErrorKind::InvalidUnicode)?; + let n = u32::from_str_radix(&take(&mut chars, 8), 16).map_err( + |err| NodeErrorKind::ParsingUnicodeFailed { + file_name: s.to_string(), + target: "u32".to_string(), + chars, + source: err, + }, + )?; + let c = std::char::from_u32(n).ok_or( + NodeErrorKind::InvalidUnicode { + file_name: s.to_string(), + unicode: n, + chars, + }, + )?; let mut bytes = vec![0u8; c.len_utf8()]; _ = c.encode_utf8(&mut bytes); u.extend_from_slice(&bytes); } - _ => return Err(NodeErrorKind::UnrecognizedEscape.into()), + _ => { + return Err(NodeErrorKind::UnrecognizedEscape { + file_name: s.to_string(), + chars, + }) + } }, } } else { diff --git a/crates/core/src/blob/packer.rs b/crates/core/src/blob/packer.rs index 2042c900..bf31b5ed 100644 --- a/crates/core/src/blob/packer.rs +++ b/crates/core/src/blob/packer.rs @@ -1,6 +1,3 @@ -use integer_sqrt::IntegerSquareRoot; -use log::warn; - use std::{ num::NonZeroU32, sync::{Arc, RwLock}, @@ -10,6 +7,8 @@ use std::{ use bytes::{Bytes, BytesMut}; use chrono::Local; use crossbeam_channel::{bounded, Receiver, Sender}; +use integer_sqrt::IntegerSquareRoot; +use log::warn; use pariter::{scope, IteratorExt}; use crate::{ @@ -19,7 +18,7 @@ use crate::{ }, blob::{BlobId, BlobType}, crypto::{hasher::hash, CryptoKey}, - error::{PackerErrorKind, RusticErrorKind, RusticResult}, + error::RusticResult, index::indexer::SharedIndexer, repofile::{ configfile::ConfigFile, @@ -265,12 +264,13 @@ impl Packer { |(_, id, _, _, _)| !indexer.read().unwrap().has(id), ) }) - .try_for_each(|item: RusticResult<_>| { + .try_for_each(|item: RusticResult<_>| -> RusticResult<()> { let (data, id, data_len, ul, size_limit) = item?; raw_packer .write() .unwrap() .add_raw(&data, &id, data_len, ul, size_limit) + .map_err(|_err| todo!("Error transition")) }) .and_then(|()| raw_packer.write().unwrap().finalize()); _ = finish_tx.send(status); @@ -296,6 +296,7 @@ impl Packer { pub fn add(&self, data: Bytes, id: BlobId) -> RusticResult<()> { // compute size limit based on total size and size bounds self.add_with_sizelimit(data, id, None) + .map_err(|_err| todo!("Error transition")) } /// Adds the blob to the packfile, allows specifying a size limit for the pack file @@ -316,10 +317,10 @@ impl Packer { data: Bytes, id: BlobId, size_limit: Option, - ) -> RusticResult<()> { + ) -> PackerResult<()> { self.sender .send((data, id, size_limit)) - .map_err(PackerErrorKind::SendingCrossbeamMessageFailed)?; + .map_err(|_err| todo!("Error transition"))?; Ok(()) } @@ -344,7 +345,7 @@ impl Packer { data_len: u64, uncompressed_length: Option, size_limit: Option, - ) -> RusticResult<()> { + ) -> PackerResult<()> { // only add if this blob is not present if self.indexer.read().unwrap().has(id) { Ok(()) @@ -368,7 +369,9 @@ impl Packer { // cancel channel drop(self.sender); // wait for items in channel to be processed - self.finish.recv().unwrap() + self.finish + .recv() + .expect("Should be able to receive from channel to finalize packer.") } } @@ -494,7 +497,7 @@ impl RawPacker { /// /// If the packfile could not be saved fn finalize(&mut self) -> RusticResult { - self.save()?; + self.save().map_err(|_err| todo!("Error transition"))?; self.file_writer.take().unwrap().finalize()?; Ok(std::mem::take(&mut self.stats)) } @@ -508,11 +511,15 @@ impl RawPacker { /// # Returns /// /// The number of bytes written. - fn write_data(&mut self, data: &[u8]) -> RusticResult { + fn write_data(&mut self, data: &[u8]) -> PackerResult { let len = data .len() .try_into() - .map_err(PackerErrorKind::IntConversionFailed)?; + .map_err(|err| PackerErrorKind::ConversionFailed { + to: "u32", + from: "usize", + source: err, + })?; self.file.extend_from_slice(data); self.size += len; Ok(len) @@ -540,16 +547,20 @@ impl RawPacker { data_len: u64, uncompressed_length: Option, size_limit: Option, - ) -> RusticResult<()> { + ) -> PackerResult<()> { if self.has(id) { return Ok(()); } self.stats.blobs += 1; self.stats.data += data_len; - let data_len_packed: u64 = data - .len() - .try_into() - .map_err(PackerErrorKind::IntConversionFailed)?; + let data_len_packed: u64 = + data.len() + .try_into() + .map_err(|err| PackerErrorKind::ConversionFailed { + to: "u64", + from: "usize", + source: err, + })?; self.stats.data_packed += data_len_packed; let size_limit = size_limit.unwrap_or_else(|| self.pack_sizer.pack_size()); @@ -586,21 +597,35 @@ impl RawPacker { /// /// [`PackerErrorKind::IntConversionFailed`]: crate::error::PackerErrorKind::IntConversionFailed /// [`PackFileErrorKind::WritingBinaryRepresentationFailed`]: crate::error::PackFileErrorKind::WritingBinaryRepresentationFailed - fn write_header(&mut self) -> RusticResult<()> { + fn write_header(&mut self) -> PackerResult<()> { // compute the pack header - let data = PackHeaderRef::from_index_pack(&self.index).to_binary()?; + let data = PackHeaderRef::from_index_pack(&self.index) + .to_binary() + .map_err(|_err| todo!("Error transition"))?; // encrypt and write to pack file - let data = self.be.key().encrypt_data(&data)?; + let data = self + .be + .key() + .encrypt_data(&data) + .map_err(|_err| todo!("Error transition"))?; let headerlen = data .len() .try_into() - .map_err(PackerErrorKind::IntConversionFailed)?; + .map_err(|err| PackerErrorKind::ConversionFailed { + to: "u32", + from: "usize", + source: err, + })?; _ = self.write_data(&data)?; // finally write length of header unencrypted to pack file - _ = self.write_data(&PackHeaderLength::from_u32(headerlen).to_binary()?)?; + _ = self.write_data( + &PackHeaderLength::from_u32(headerlen) + .to_binary() + .map_err(|_err| todo!("Error transition"))?, + )?; Ok(()) } @@ -618,7 +643,7 @@ impl RawPacker { /// /// [`PackerErrorKind::IntConversionFailed`]: crate::error::PackerErrorKind::IntConversionFailed /// [`PackFileErrorKind::WritingBinaryRepresentationFailed`]: crate::error::PackFileErrorKind::WritingBinaryRepresentationFailed - fn save(&mut self) -> RusticResult<()> { + fn save(&mut self) -> PackerResult<()> { if self.size == 0 { return Ok(()); } @@ -661,8 +686,7 @@ impl FileWriterHandle { let (file, id, mut index) = load; index.id = id; self.be - .write_bytes(FileType::Pack, &id, self.cacheable, file) - .map_err(RusticErrorKind::Backend)?; + .write_bytes(FileType::Pack, &id, self.cacheable, file)?; index.time = Some(Local::now()); Ok(index) } @@ -734,10 +758,10 @@ impl Actor { /// # Errors /// /// If sending the message to the actor fails. - fn send(&self, load: (Bytes, IndexPack)) -> RusticResult<()> { + fn send(&self, load: (Bytes, IndexPack)) -> PackerResult<()> { self.sender .send(load) - .map_err(PackerErrorKind::SendingCrossbeamMessageFailedForIndexPack)?; + .map_err(|_err| todo!("Error transition"))?; Ok(()) } @@ -818,23 +842,22 @@ impl Repacker { /// If the blob could not be added /// If reading the blob from the backend fails pub fn add_fast(&self, pack_id: &PackId, blob: &IndexBlob) -> RusticResult<()> { - let data = self - .be - .read_partial( - FileType::Pack, - pack_id, - blob.tpe.is_cacheable(), - blob.offset, - blob.length, - ) - .map_err(RusticErrorKind::Backend)?; - self.packer.add_raw( - &data, - &blob.id, - 0, - blob.uncompressed_length, - Some(self.size_limit), + let data = self.be.read_partial( + FileType::Pack, + pack_id, + blob.tpe.is_cacheable(), + blob.offset, + blob.length, )?; + self.packer + .add_raw( + &data, + &blob.id, + 0, + blob.uncompressed_length, + Some(self.size_limit), + ) + .map_err(|_err| todo!("Error transition"))?; Ok(()) } @@ -858,8 +881,11 @@ impl Repacker { blob.length, blob.uncompressed_length, )?; + self.packer - .add_with_sizelimit(data, blob.id, Some(self.size_limit))?; + .add_with_sizelimit(data, blob.id, Some(self.size_limit)) + .map_err(|_err| todo!("Error transition"))?; + Ok(()) } diff --git a/crates/core/src/blob/tree.rs b/crates/core/src/blob/tree.rs index acb19eff..7d1338b6 100644 --- a/crates/core/src/blob/tree.rs +++ b/crates/core/src/blob/tree.rs @@ -4,7 +4,7 @@ use std::{ ffi::{OsStr, OsString}, mem, path::{Component, Path, PathBuf, Prefix}, - str, + str::{self, Utf8Error}, }; use crossbeam_channel::{bounded, unbounded, Receiver, Sender}; @@ -12,7 +12,6 @@ use derivative::Derivative; use derive_setters::Setters; use ignore::overrides::{Override, OverrideBuilder}; use ignore::Match; - use serde::{Deserialize, Deserializer}; use serde_derive::Serialize; @@ -23,7 +22,7 @@ use crate::{ }, blob::BlobType, crypto::hasher::hash, - error::{RusticResult, TreeErrorKind}, + error::RusticResult, impl_blobid, index::ReadGlobalIndex, progress::Progress, @@ -112,10 +111,15 @@ impl Tree { /// # Returns /// /// A tuple of the serialized tree as `Vec` and the tree's ID - pub(crate) fn serialize(&self) -> RusticResult<(Vec, TreeId)> { + pub(crate) fn serialize(&self) -> TreeResult<(Vec, TreeId)> { let mut chunk = serde_json::to_vec(&self).map_err(TreeErrorKind::SerializingTreeFailed)?; - chunk.push(b'\n'); // for whatever reason, restic adds a newline, so to be compatible... + // # COMPATIBILITY + // + // We add a newline to be compatible with `restic` here + chunk.push(b'\n'); + let id = hash(&chunk).into(); + Ok((chunk, id)) } @@ -144,10 +148,15 @@ impl Tree { ) -> RusticResult { let data = index .get_tree(&id) - .ok_or_else(|| TreeErrorKind::BlobIdNotFound(id))? + .ok_or_else(|| TreeErrorKind::BlobIdNotFound(id)) + .map_err(|_err| todo!("Error transition"))? .read_data(be)?; - Ok(serde_json::from_slice(&data).map_err(TreeErrorKind::DeserializingTreeFailed)?) + let tree = serde_json::from_slice(&data) + .map_err(TreeErrorKind::DeserializingTreeFailed) + .map_err(|_err| todo!("Error transition"))?; + + Ok(tree) } /// Creates a new node from a path. @@ -177,16 +186,18 @@ impl Tree { node.subtree = Some(id); for p in path.components() { - if let Some(p) = comp_to_osstr(p)? { + if let Some(p) = comp_to_osstr(p).map_err(|_err| todo!("Error transition"))? { let id = node .subtree - .ok_or_else(|| TreeErrorKind::NotADirectory(p.clone()))?; + .ok_or_else(|| TreeErrorKind::NotADirectory(p.clone())) + .map_err(|_err| todo!("Error transition"))?; let tree = Self::from_backend(be, index, id)?; node = tree .nodes .into_iter() .find(|node| node.name() == p) - .ok_or_else(|| TreeErrorKind::PathNotFound(p.clone()))?; + .ok_or_else(|| TreeErrorKind::PathNotFound(p.clone())) + .map_err(|_err| todo!("Error transition"))?; } } @@ -226,7 +237,8 @@ impl Tree { } else { let id = node .subtree - .ok_or_else(|| TreeErrorKind::NotADirectory(path_comp[idx].clone()))?; + .ok_or_else(|| TreeErrorKind::NotADirectory(path_comp[idx].clone())) + .map_err(|_err| todo!("Error transition"))?; find_node_from_component( be, @@ -248,7 +260,8 @@ impl Tree { let path_comp: Vec<_> = path .components() .filter_map(|p| comp_to_osstr(p).transpose()) - .collect::>()?; + .collect::>() + .map_err(|_err| todo!("Error transition"))?; // caching all results let mut results_cache = vec![BTreeMap::new(); path_comp.len()]; @@ -322,7 +335,8 @@ impl Tree { if node.is_dir() { let id = node .subtree - .ok_or_else(|| TreeErrorKind::NotADirectory(node.name()))?; + .ok_or_else(|| TreeErrorKind::NotADirectory(node.name())) + .map_err(|_err| todo!("Error transition"))?; result.append(&mut find_matching_nodes_recursive( be, index, id, &node_path, state, matches, )?); @@ -397,7 +411,7 @@ pub struct FindMatches { /// /// [`TreeErrorKind::ContainsCurrentOrParentDirectory`]: crate::error::TreeErrorKind::ContainsCurrentOrParentDirectory /// [`TreeErrorKind::PathIsNotUtf8Conform`]: crate::error::TreeErrorKind::PathIsNotUtf8Conform -pub(crate) fn comp_to_osstr(p: Component<'_>) -> RusticResult> { +pub(crate) fn comp_to_osstr(p: Component<'_>) -> TreeResult> { let s = match p { Component::RootDir => None, Component::Prefix(p) => match p.kind() { @@ -503,7 +517,6 @@ where /// /// [`TreeErrorKind::BlobIdNotFound`]: crate::error::TreeErrorKind::BlobIdNotFound /// [`TreeErrorKind::DeserializingTreeFailed`]: crate::error::TreeErrorKind::DeserializingTreeFailed - #[allow(unused)] pub fn new(be: BE, index: &'a I, node: &Node) -> RusticResult { Self::new_streamer(be, index, node, None, true) } @@ -575,42 +588,50 @@ where for g in &opts.glob { _ = override_builder .add(g) - .map_err(TreeErrorKind::BuildingNodeStreamerFailed)?; + .map_err(TreeErrorKind::BuildingNodeStreamerFailed) + .map_err(|_err| todo!("Error transition"))?; } for file in &opts.glob_file { for line in std::fs::read_to_string(file) - .map_err(TreeErrorKind::ReadingFileStringFromGlobsFailed)? + .map_err(TreeErrorKind::ReadingFileStringFromGlobsFailed) + .map_err(|_err| todo!("Error transition"))? .lines() { _ = override_builder .add(line) - .map_err(TreeErrorKind::BuildingNodeStreamerFailed)?; + .map_err(TreeErrorKind::BuildingNodeStreamerFailed) + .map_err(|_err| todo!("Error transition"))?; } } _ = override_builder .case_insensitive(true) - .map_err(TreeErrorKind::BuildingNodeStreamerFailed)?; + .map_err(TreeErrorKind::BuildingNodeStreamerFailed) + .map_err(|_err| todo!("Error transition"))?; for g in &opts.iglob { _ = override_builder .add(g) - .map_err(TreeErrorKind::BuildingNodeStreamerFailed)?; + .map_err(TreeErrorKind::BuildingNodeStreamerFailed) + .map_err(|_err| todo!("Error transition"))?; } for file in &opts.iglob_file { for line in std::fs::read_to_string(file) - .map_err(TreeErrorKind::ReadingFileStringFromGlobsFailed)? + .map_err(TreeErrorKind::ReadingFileStringFromGlobsFailed) + .map_err(|_err| todo!("Error transition"))? .lines() { _ = override_builder .add(line) - .map_err(TreeErrorKind::BuildingNodeStreamerFailed)?; + .map_err(TreeErrorKind::BuildingNodeStreamerFailed) + .map_err(|_err| todo!("Error transition"))?; } } let overrides = override_builder .build() - .map_err(TreeErrorKind::BuildingNodeStreamerFailed)?; + .map_err(TreeErrorKind::BuildingNodeStreamerFailed) + .map_err(|_err| todo!("Error transition"))?; Self::new_streamer(be, index, node, Some(overrides), opts.recursive) } @@ -738,7 +759,10 @@ impl TreeStreamerOnce

{ }; for (count, id) in ids.into_iter().enumerate() { - if !streamer.add_pending(PathBuf::new(), id, count)? { + if !streamer + .add_pending(PathBuf::new(), id, count) + .map_err(|_err| todo!("Error transition"))? + { streamer.p.inc(1); streamer.finished_ids += 1; } @@ -764,13 +788,17 @@ impl TreeStreamerOnce

{ /// * [`TreeErrorKind::SendingCrossbeamMessageFailed`] - If sending the message fails. /// /// [`TreeErrorKind::SendingCrossbeamMessageFailed`]: crate::error::TreeErrorKind::SendingCrossbeamMessageFailed - fn add_pending(&mut self, path: PathBuf, id: TreeId, count: usize) -> RusticResult { + fn add_pending(&mut self, path: PathBuf, id: TreeId, count: usize) -> TreeResult { if self.visited.insert(id) { self.queue_in .as_ref() .unwrap() .send((path, id, count)) - .map_err(TreeErrorKind::SendingCrossbeamMessageFailed)?; + .map_err(|err| TreeErrorKind::Channel { + kind: "sending crossbeam message", + source: err.into(), + })?; + self.counter[count] += 1; Ok(true) } else { @@ -791,9 +819,13 @@ impl Iterator for TreeStreamerOnce

{ let (path, tree, count) = match self.queue_out.recv() { Ok(Ok(res)) => res, Err(err) => { - return Some(Err( - TreeErrorKind::ReceivingCrossbreamMessageFailed(err).into() - )) + return Some( + Err(TreeErrorKind::Channel { + kind: "receiving crossbeam message", + source: err.into(), + }) + .map_err(|_err| todo!("Error transition")), + ) } Ok(Err(err)) => return Some(Err(err)), }; @@ -804,7 +836,7 @@ impl Iterator for TreeStreamerOnce

{ path.push(node.name()); match self.add_pending(path, id, count) { Ok(_) => {} - Err(err) => return Some(Err(err)), + Err(err) => return Some(Err(err).map_err(|_err| todo!("Error transition"))), } } } diff --git a/crates/core/src/chunker.rs b/crates/core/src/chunker.rs index 530ee7f0..93d64965 100644 --- a/crates/core/src/chunker.rs +++ b/crates/core/src/chunker.rs @@ -199,7 +199,8 @@ pub fn random_poly() -> RusticResult { return Ok(poly); } } - Err(PolynomialErrorKind::NoSuitablePolynomialFound.into()) + + todo!("create rustic error Err(PolynomialErrorKind::NoSuitablePolynomialFound)"); } /// A trait for extending polynomials. diff --git a/crates/core/src/commands/backup.rs b/crates/core/src/commands/backup.rs index 1d6b60d0..bfac5ba9 100644 --- a/crates/core/src/commands/backup.rs +++ b/crates/core/src/commands/backup.rs @@ -229,12 +229,22 @@ pub(crate) fn backup( let as_path = opts .as_path .as_ref() - .map(|p| -> RusticResult<_> { Ok(p.parse_dot()?.to_path_buf()) }) + .map(|p| -> RusticResult<_> { + Ok(p.parse_dot() + .map_err(|_err| todo!("Error transition"))? + .to_path_buf()) + }) .transpose()?; match &as_path { - Some(p) => snap.paths.set_paths(&[p.clone()])?, - None => snap.paths.set_paths(&backup_path)?, + Some(p) => snap + .paths + .set_paths(&[p.clone()]) + .map_err(|_err| todo!("Error transition"))?, + None => snap + .paths + .set_paths(&backup_path) + .map_err(|_err| todo!("Error transition"))?, }; let (parent_id, parent) = opts.parent_opts.get_parent(repo, &snap, backup_stdin); diff --git a/crates/core/src/commands/cat.rs b/crates/core/src/commands/cat.rs index 9c45a077..38fa566d 100644 --- a/crates/core/src/commands/cat.rs +++ b/crates/core/src/commands/cat.rs @@ -5,7 +5,7 @@ use bytes::Bytes; use crate::{ backend::{decrypt::DecryptReadBackend, FileType, FindInBackend}, blob::{tree::Tree, BlobId, BlobType}, - error::{CommandErrorKind, RusticResult}, + error::RusticResult, index::ReadIndex, progress::ProgressBars, repofile::SnapshotFile, @@ -114,7 +114,8 @@ pub(crate) fn cat_tree( let node = Tree::node_from_path(repo.dbe(), repo.index(), snap.tree, Path::new(path))?; let id = node .subtree - .ok_or_else(|| CommandErrorKind::PathIsNoDir(path.to_string()))?; + .ok_or_else(|| CommandErrorKind::PathIsNoDir(path.to_string())) + .map_err(|_err| todo!("Error transition"))?; let data = repo .index() .blob_from_backend(repo.dbe(), BlobType::Tree, &BlobId::from(*id))?; diff --git a/crates/core/src/commands/check.rs b/crates/core/src/commands/check.rs index 6b6cb3ca..743259a6 100644 --- a/crates/core/src/commands/check.rs +++ b/crates/core/src/commands/check.rs @@ -2,6 +2,7 @@ use std::{ collections::{BTreeSet, HashMap}, fmt::Debug, + path::PathBuf, str::FromStr, }; @@ -18,7 +19,7 @@ use crate::{ backend::{cache::Cache, decrypt::DecryptReadBackend, node::NodeType, FileType, ReadBackend}, blob::{tree::TreeStreamerOnce, BlobId, BlobType}, crypto::hasher::hash, - error::{CommandErrorKind, RusticErrorKind, RusticResult}, + error::{RusticError, RusticResult}, id::Id, index::{ binarysorted::{IndexCollector, IndexType}, @@ -191,8 +192,12 @@ impl ReadSubsetOption { } } -/// parses n/m inclding named settings depending on current date -fn parse_n_m(now: NaiveDateTime, n_in: &str, m_in: &str) -> Result<(u32, u32), CommandErrorKind> { +/// parses n/m including named settings depending on current date +fn parse_n_m( + now: NaiveDateTime, + n_in: &str, + m_in: &str, +) -> Result<(u32, u32), CheckCommandErrorKind> { let is_leap_year = |dt: NaiveDateTime| { let year = dt.year(); year % 4 == 0 && (year % 25 != 0 || year % 16 == 0) @@ -233,7 +238,7 @@ fn parse_n_m(now: NaiveDateTime, n_in: &str, m_in: &str) -> Result<(u32, u32), C } impl FromStr for ReadSubsetOption { - type Err = CommandErrorKind; + type Err = CheckCommandErrorKind; fn from_str(s: &str) -> Result { let result = if s == "all" { Self::All @@ -246,7 +251,7 @@ impl FromStr for ReadSubsetOption { } else { Self::Size( ByteSize::from_str(s) - .map_err(CommandErrorKind::FromByteSizeParser)? + .map_err(CheckCommandErrorKind::FromByteSizeParser)? .as_u64(), ) }; diff --git a/crates/core/src/commands/config.rs b/crates/core/src/commands/config.rs index 4f1e2446..29122138 100644 --- a/crates/core/src/commands/config.rs +++ b/crates/core/src/commands/config.rs @@ -1,11 +1,13 @@ //! `config` subcommand +use std::ops::RangeInclusive; + use bytesize::ByteSize; use derive_setters::Setters; use crate::{ backend::decrypt::{DecryptBackend, DecryptWriteBackend}, crypto::CryptoKey, - error::{CommandErrorKind, RusticResult}, + error::RusticResult, repofile::ConfigFile, repository::{Open, Repository}, }; @@ -47,34 +49,38 @@ pub(crate) type ConfigCommandResult = Result; /// /// # Errors /// -/// * [`CommandErrorKind::VersionNotSupported`] - If the version is not supported -/// * [`CommandErrorKind::CannotDowngrade`] - If the version is lower than the current version -/// * [`CommandErrorKind::NoCompressionV1Repo`] - If compression is set for a v1 repo -/// * [`CommandErrorKind::CompressionLevelNotSupported`] - If the compression level is not supported -/// * [`CommandErrorKind::SizeTooLarge`] - If the size is too large -/// * [`CommandErrorKind::MinPackSizeTolerateWrong`] - If the min packsize tolerance percent is wrong -/// * [`CommandErrorKind::MaxPackSizeTolerateWrong`] - If the max packsize tolerance percent is wrong +/// * [`ConfigCommandErrorKind::VersionNotSupported`] - If the version is not supported +/// * [`ConfigCommandErrorKind::CannotDowngrade`] - If the version is lower than the current version +/// * [`ConfigCommandErrorKind::NoCompressionV1Repo`] - If compression is set for a v1 repo +/// * [`ConfigCommandErrorKind::CompressionLevelNotSupported`] - If the compression level is not supported +/// * [`ConfigCommandErrorKind::SizeTooLarge`] - If the size is too large +/// * [`ConfigCommandErrorKind::MinPackSizeTolerateWrong`] - If the min packsize tolerance percent is wrong +/// * [`ConfigCommandErrorKind::MaxPackSizeTolerateWrong`] - If the max packsize tolerance percent is wrong /// * [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`] - If the file could not be serialized to json. /// /// # Returns /// /// Whether the config was changed /// -/// [`CommandErrorKind::VersionNotSupported`]: crate::error::CommandErrorKind::VersionNotSupported -/// [`CommandErrorKind::CannotDowngrade`]: crate::error::CommandErrorKind::CannotDowngrade -/// [`CommandErrorKind::NoCompressionV1Repo`]: crate::error::CommandErrorKind::NoCompressionV1Repo -/// [`CommandErrorKind::CompressionLevelNotSupported`]: crate::error::CommandErrorKind::CompressionLevelNotSupported -/// [`CommandErrorKind::SizeTooLarge`]: crate::error::CommandErrorKind::SizeTooLarge -/// [`CommandErrorKind::MinPackSizeTolerateWrong`]: crate::error::CommandErrorKind::MinPackSizeTolerateWrong -/// [`CommandErrorKind::MaxPackSizeTolerateWrong`]: crate::error::CommandErrorKind::MaxPackSizeTolerateWrong +/// [`ConfigCommandErrorKind::VersionNotSupported`]: ConfigCommandErrorKind::VersionNotSupported +/// [`ConfigCommandErrorKind::CannotDowngrade`]: ConfigCommandErrorKind::CannotDowngrade +/// [`ConfigCommandErrorKind::NoCompressionV1Repo`]: ConfigCommandErrorKind::NoCompressionV1Repo +/// [`ConfigCommandErrorKind::CompressionLevelNotSupported`]: ConfigCommandErrorKind::CompressionLevelNotSupported +/// [`ConfigCommandErrorKind::SizeTooLarge`]: ConfigCommandErrorKind::SizeTooLarge +/// [`ConfigCommandErrorKind::MinPackSizeTolerateWrong`]: ConfigCommandErrorKind::MinPackSizeTolerateWrong +/// [`ConfigCommandErrorKind::MaxPackSizeTolerateWrong`]: ConfigCommandErrorKind::MaxPackSizeTolerateWrong /// [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`]: crate::error::CryptBackendErrorKind::SerializingToJsonByteVectorFailed pub(crate) fn apply_config( repo: &Repository, opts: &ConfigOptions, ) -> RusticResult { if repo.config().append_only == Some(true) { - return Err(CommandErrorKind::NotAllowedWithAppendOnly("config change".to_string()).into()); + return Err(ConfigCommandErrorKind::NotAllowedWithAppendOnly( + "config change".to_string(), + )) + .map_err(|_err| todo!("Error transition")); } + let mut new_config = repo.config().clone(); opts.apply(&mut new_config)?; if &new_config == repo.config() { @@ -115,10 +121,11 @@ pub(crate) fn save_config( if let Some(hot_be) = repo.be_hot.clone() { // save config to hot repo - let dbe = DecryptBackend::new(hot_be, key); + let dbe = DecryptBackend::new(hot_be.clone(), key); new_config.is_hot = Some(true); _ = dbe.save_file_uncompressed(&new_config)?; } + Ok(()) } @@ -206,41 +213,49 @@ impl ConfigOptions { /// /// # Errors /// - /// * [`CommandErrorKind::VersionNotSupported`] - If the version is not supported - /// * [`CommandErrorKind::CannotDowngrade`] - If the version is lower than the current version - /// * [`CommandErrorKind::NoCompressionV1Repo`] - If compression is set for a v1 repo - /// * [`CommandErrorKind::CompressionLevelNotSupported`] - If the compression level is not supported - /// * [`CommandErrorKind::SizeTooLarge`] - If the size is too large - /// * [`CommandErrorKind::MinPackSizeTolerateWrong`] - If the min packsize tolerate percent is wrong - /// * [`CommandErrorKind::MaxPackSizeTolerateWrong`] - If the max packsize tolerate percent is wrong + /// * [`ConfigCommandErrorKind::VersionNotSupported`] - If the version is not supported + /// * [`ConfigCommandErrorKind::CannotDowngrade`] - If the version is lower than the current version + /// * [`ConfigCommandErrorKind::NoCompressionV1Repo`] - If compression is set for a v1 repo + /// * [`ConfigCommandErrorKind::CompressionLevelNotSupported`] - If the compression level is not supported + /// * [`ConfigCommandErrorKind::SizeTooLarge`] - If the size is too large + /// * [`ConfigCommandErrorKind::MinPackSizeTolerateWrong`] - If the min packsize tolerate percent is wrong + /// * [`ConfigCommandErrorKind::MaxPackSizeTolerateWrong`] - If the max packsize tolerate percent is wrong /// - /// [`CommandErrorKind::VersionNotSupported`]: crate::error::CommandErrorKind::VersionNotSupported - /// [`CommandErrorKind::CannotDowngrade`]: crate::error::CommandErrorKind::CannotDowngrade - /// [`CommandErrorKind::NoCompressionV1Repo`]: crate::error::CommandErrorKind::NoCompressionV1Repo - /// [`CommandErrorKind::CompressionLevelNotSupported`]: crate::error::CommandErrorKind::CompressionLevelNotSupported - /// [`CommandErrorKind::SizeTooLarge`]: crate::error::CommandErrorKind::SizeTooLarge - /// [`CommandErrorKind::MinPackSizeTolerateWrong`]: crate::error::CommandErrorKind::MinPackSizeTolerateWrong - /// [`CommandErrorKind::MaxPackSizeTolerateWrong`]: crate::error::CommandErrorKind::MaxPackSizeTolerateWrong + /// [`ConfigCommandErrorKind::VersionNotSupported`]: ConfigCommandErrorKind::VersionNotSupported + /// [`ConfigCommandErrorKind::CannotDowngrade`]: ConfigCommandErrorKind::CannotDowngrade + /// [`ConfigCommandErrorKind::NoCompressionV1Repo`]: ConfigCommandErrorKind::NoCompressionV1Repo + /// [`ConfigCommandErrorKind::CompressionLevelNotSupported`]: ConfigCommandErrorKind::CompressionLevelNotSupported + /// [`ConfigCommandErrorKind::SizeTooLarge`]: ConfigCommandErrorKind::SizeTooLarge + /// [`ConfigCommandErrorKind::MinPackSizeTolerateWrong`]: ConfigCommandErrorKind::MinPackSizeTolerateWrong + /// [`ConfigCommandErrorKind::MaxPackSizeTolerateWrong`]: ConfigCommandErrorKind::MaxPackSizeTolerateWrong pub fn apply(&self, config: &mut ConfigFile) -> RusticResult<()> { if let Some(version) = self.set_version { let range = 1..=2; if !range.contains(&version) { - return Err(CommandErrorKind::VersionNotSupported(version, range).into()); + return Err(ConfigCommandErrorKind::VersionNotSupported(version, range)) + .map_err(|_err| todo!("Error transition")); } else if version < config.version { - return Err(CommandErrorKind::CannotDowngrade(config.version, version).into()); + return Err(ConfigCommandErrorKind::CannotDowngrade( + config.version, + version, + )) + .map_err(|_err| todo!("Error transition")); } config.version = version; } if let Some(compression) = self.set_compression { if config.version == 1 && compression != 0 { - return Err(CommandErrorKind::NoCompressionV1Repo(compression).into()); + return Err(ConfigCommandErrorKind::NoCompressionV1Repo(compression)) + .map_err(|_err| todo!("Error transition")); } let range = zstd::compression_level_range(); if !range.contains(&compression) { - return Err( - CommandErrorKind::CompressionLevelNotSupported(compression, range).into(), - ); + return Err(ConfigCommandErrorKind::CompressionLevelNotSupported( + compression, + range, + )) + .map_err(|_err| todo!("Error transition")); } config.compression = Some(compression); } @@ -253,7 +268,8 @@ impl ConfigOptions { config.treepack_size = Some( size.as_u64() .try_into() - .map_err(|_| CommandErrorKind::SizeTooLarge(size))?, + .map_err(|_| ConfigCommandErrorKind::SizeTooLarge(size)) + .map_err(|_err| todo!("Error transition"))?, ); } if let Some(factor) = self.set_treepack_growfactor { @@ -263,7 +279,8 @@ impl ConfigOptions { config.treepack_size_limit = Some( size.as_u64() .try_into() - .map_err(|_| CommandErrorKind::SizeTooLarge(size))?, + .map_err(|_| ConfigCommandErrorKind::SizeTooLarge(size)) + .map_err(|_err| todo!("Error transition"))?, ); } @@ -271,7 +288,8 @@ impl ConfigOptions { config.datapack_size = Some( size.as_u64() .try_into() - .map_err(|_| CommandErrorKind::SizeTooLarge(size))?, + .map_err(|_| ConfigCommandErrorKind::SizeTooLarge(size)) + .map_err(|_err| todo!("Error transition"))?, ); } if let Some(factor) = self.set_datapack_growfactor { @@ -281,20 +299,23 @@ impl ConfigOptions { config.datapack_size_limit = Some( size.as_u64() .try_into() - .map_err(|_| CommandErrorKind::SizeTooLarge(size))?, + .map_err(|_| ConfigCommandErrorKind::SizeTooLarge(size)) + .map_err(|_err| todo!("Error transition"))?, ); } if let Some(percent) = self.set_min_packsize_tolerate_percent { if percent > 100 { - return Err(CommandErrorKind::MinPackSizeTolerateWrong.into()); + return Err(ConfigCommandErrorKind::MinPackSizeTolerateWrong) + .map_err(|_err| todo!("Error transition")); } config.min_packsize_tolerate_percent = Some(percent); } if let Some(percent) = self.set_max_packsize_tolerate_percent { if percent < 100 && percent > 0 { - return Err(CommandErrorKind::MaxPackSizeTolerateWrong.into()); + return Err(ConfigCommandErrorKind::MaxPackSizeTolerateWrong) + .map_err(|_err| todo!("Error transition")); } config.max_packsize_tolerate_percent = Some(percent); } diff --git a/crates/core/src/commands/dump.rs b/crates/core/src/commands/dump.rs index 503a998c..52ccaa1e 100644 --- a/crates/core/src/commands/dump.rs +++ b/crates/core/src/commands/dump.rs @@ -3,7 +3,7 @@ use std::io::Write; use crate::{ backend::node::{Node, NodeType}, blob::{BlobId, BlobType}, - error::{CommandErrorKind, RusticResult}, + error::RusticResult, repository::{IndexedFull, Repository}, }; @@ -31,12 +31,14 @@ pub(crate) fn dump( w: &mut impl Write, ) -> RusticResult<()> { if node.node_type != NodeType::File { - return Err(CommandErrorKind::DumpNotSupported(node.node_type.clone()).into()); + return Err(CommandErrorKind::DumpNotSupported(node.node_type.clone()).into()) + .map_err(|_err| todo!("Error transition")); } for id in node.content.as_ref().unwrap() { let data = repo.get_blob_cached(&BlobId::from(**id), BlobType::Data)?; - w.write_all(&data)?; + w.write_all(&data) + .map_err(|_err| todo!("Error transition"))?; } Ok(()) } diff --git a/crates/core/src/commands/forget.rs b/crates/core/src/commands/forget.rs index 0bfd4c42..dfe76e71 100644 --- a/crates/core/src/commands/forget.rs +++ b/crates/core/src/commands/forget.rs @@ -6,7 +6,7 @@ use serde_derive::{Deserialize, Serialize}; use serde_with::{serde_as, skip_serializing_none, DisplayFromStr}; use crate::{ - error::{CommandErrorKind, RusticResult}, + error::RusticResult, progress::ProgressBars, repofile::{ snapshotfile::{SnapshotGroup, SnapshotGroupCriterion, SnapshotId}, @@ -523,7 +523,7 @@ impl KeepOptions { now: DateTime, ) -> RusticResult> { if !self.is_valid() { - return Err(CommandErrorKind::NoKeepOption.into()); + return Err(CommandErrorKind::NoKeepOption).map_err(|_err| todo!("Error transition")); } let mut group_keep = self.clone(); diff --git a/crates/core/src/commands/init.rs b/crates/core/src/commands/init.rs index f932658c..6047acb8 100644 --- a/crates/core/src/commands/init.rs +++ b/crates/core/src/commands/init.rs @@ -10,7 +10,7 @@ use crate::{ key::{init_key, KeyOptions}, }, crypto::aespoly1305::Key, - error::{RusticErrorKind, RusticResult}, + error::RusticResult, id::Id, repofile::{configfile::RepositoryId, ConfigFile}, repository::Repository, @@ -85,7 +85,7 @@ pub(crate) fn init_with_config( key_opts: &KeyOptions, config: &ConfigFile, ) -> RusticResult { - repo.be.create().map_err(RusticErrorKind::Backend)?; + repo.be.create()?; let (key, id) = init_key(repo, key_opts, pass)?; info!("key {id} successfully added."); save_config(repo, config.clone(), key)?; diff --git a/crates/core/src/commands/key.rs b/crates/core/src/commands/key.rs index af277665..ce2aba59 100644 --- a/crates/core/src/commands/key.rs +++ b/crates/core/src/commands/key.rs @@ -4,7 +4,7 @@ use derive_setters::Setters; use crate::{ backend::{decrypt::DecryptWriteBackend, FileType, WriteBackend}, crypto::{aespoly1305::Key, hasher::hash}, - error::{CommandErrorKind, RusticErrorKind, RusticResult}, + error::RusticResult, repofile::{KeyFile, KeyId}, repository::{Open, Repository}, }; @@ -112,10 +112,10 @@ pub(crate) fn add_key_to_repo( let ko = opts.clone(); let keyfile = KeyFile::generate(key, &pass, ko.hostname, ko.username, ko.with_created)?; - let data = serde_json::to_vec(&keyfile).map_err(CommandErrorKind::FromJsonError)?; + let data = serde_json::to_vec(&keyfile).map_err(|_err| todo!("Error transition"))?; let id = KeyId::from(hash(&data)); repo.be .write_bytes(FileType::Key, &id, false, data.into()) - .map_err(RusticErrorKind::Backend)?; + .map_err(|_err| todo!("Error transition"))?; Ok(id) } diff --git a/crates/core/src/commands/merge.rs b/crates/core/src/commands/merge.rs index 561411fb..c8cbc193 100644 --- a/crates/core/src/commands/merge.rs +++ b/crates/core/src/commands/merge.rs @@ -11,7 +11,7 @@ use crate::{ tree::{self, Tree, TreeId}, BlobId, BlobType, }, - error::{CommandErrorKind, RusticResult}, + error::RusticResult, index::{indexer::Indexer, ReadIndex}, progress::{Progress, ProgressBars}, repofile::{PathList, SnapshotFile, SnapshotSummary}, @@ -44,7 +44,9 @@ pub(crate) fn merge_snapshots( .collect::() .merge(); - snap.paths.set_paths(&paths.paths())?; + snap.paths + .set_paths(&paths.paths()) + .map_err(|_err| todo!("Error transition"))?; // set snapshot time to time of latest snapshot to be merged snap.time = snapshots @@ -58,7 +60,9 @@ pub(crate) fn merge_snapshots( let trees: Vec = snapshots.iter().map(|sn| sn.tree).collect(); snap.tree = merge_trees(repo, &trees, cmp, &mut summary)?; - summary.finalize(now)?; + summary + .finalize(now) + .map_err(|_err| todo!("Error transition"))?; snap.summary = Some(summary); snap.id = repo.dbe().save_file(&snap)?.into(); @@ -104,9 +108,9 @@ pub(crate) fn merge_trees( repo.config(), index.total_size(BlobType::Tree), )?; - let save = |tree: Tree| { - let (chunk, new_id) = tree.serialize()?; - let size = u64::try_from(chunk.len()).map_err(CommandErrorKind::ConversionFromIntFailed)?; + let save = |tree: Tree| -> RusticResult<_> { + let (chunk, new_id) = tree.serialize().map_err(|_err| todo!("Error transition"))?; + let size = u64::try_from(chunk.len()).map_err(|_err| todo!("Error transition"))?; if !index.has_tree(&new_id) { packer.add(chunk.into(), BlobId::from(*new_id))?; } diff --git a/crates/core/src/commands/prune.rs b/crates/core/src/commands/prune.rs index d2082f9c..c36908b6 100644 --- a/crates/core/src/commands/prune.rs +++ b/crates/core/src/commands/prune.rs @@ -30,7 +30,7 @@ use crate::{ tree::TreeStreamerOnce, BlobId, BlobType, BlobTypeMap, Initialize, }, - error::{CommandErrorKind, RusticErrorKind, RusticResult}, + error::{RusticError, RusticResult}, index::{ binarysorted::{IndexCollector, IndexType}, indexer::Indexer, @@ -198,16 +198,16 @@ pub enum LimitOption { } impl FromStr for LimitOption { - type Err = CommandErrorKind; + type Err = RusticError; fn from_str(s: &str) -> Result { Ok(match s.chars().last().unwrap_or('0') { '%' => Self::Percentage({ let mut copy = s.to_string(); _ = copy.pop(); - copy.parse()? + copy.parse().map_err(|_err| todo!("Error transition"))? }), 'd' if s == "unlimited" => Self::Unlimited, - _ => Self::Size(ByteSize::from_str(s).map_err(CommandErrorKind::FromByteSizeParser)?), + _ => Self::Size(ByteSize::from_str(s).map_err(|_err| todo!("Error transition"))?), }) } } @@ -689,7 +689,8 @@ impl PrunePlan { let be = repo.dbe(); if repo.config().version < 2 && opts.repack_uncompressed { - return Err(CommandErrorKind::RepackUncompressedRepoV1.into()); + return Err(CommandErrorKind::RepackUncompressedRepoV1) + .map_err(|_err| todo!("Error transition")); } let mut index_files = Vec::new(); @@ -718,8 +719,7 @@ impl PrunePlan { // list existing pack files let p = pb.progress_spinner("getting packs from repository..."); let existing_packs: BTreeMap<_, _> = be - .list_with_size(FileType::Pack) - .map_err(RusticErrorKind::Backend)? + .list_with_size(FileType::Pack)? .into_iter() .map(|(id, size)| (PackId::from(id), size)) .collect(); @@ -734,8 +734,8 @@ impl PrunePlan { let pack_sizer = total_size.map(|tpe, size| PackSizer::from_config(repo.config(), tpe, size)); pruner.decide_packs( - Duration::from_std(*opts.keep_pack).map_err(CommandErrorKind::FromOutOfRangeError)?, - Duration::from_std(*opts.keep_delete).map_err(CommandErrorKind::FromOutOfRangeError)?, + Duration::from_std(*opts.keep_pack).map_err(|_err| todo!("Error transition"))?, + Duration::from_std(*opts.keep_delete).map_err(|_err| todo!("Error transition"))?, repack_cacheable_only, opts.repack_uncompressed, opts.repack_all, @@ -781,7 +781,8 @@ impl PrunePlan { fn check(&self) -> RusticResult<()> { for (id, count) in &self.used_ids { if *count == 0 { - return Err(CommandErrorKind::BlobsMissing(*id).into()); + return Err(CommandErrorKind::BlobsMissing(*id)) + .map_err(|_err| todo!("Error transition")); } } Ok(()) @@ -1052,13 +1053,18 @@ impl PrunePlan { Some(size) if size == pack.size => Ok(()), // size is ok => continue Some(size) => Err(CommandErrorKind::PackSizeNotMatching( pack.id, pack.size, size, - )), - None => Err(CommandErrorKind::PackNotExisting(pack.id)), + )) + .map_err(|_err| todo!("Error transition")), + None => Err(CommandErrorKind::PackNotExisting(pack.id)) + .map_err(|_err| todo!("Error transition")), } }; match pack.to_do { - PackToDo::Undecided => return Err(CommandErrorKind::NoDecision(pack.id).into()), + PackToDo::Undecided => { + return Err(CommandErrorKind::NoDecision(pack.id).into()) + .map_err(|_err| todo!("Error transition")) + } PackToDo::Keep | PackToDo::Recover => { for blob in &pack.blobs { _ = self.used_ids.remove(&blob.id); @@ -1301,7 +1307,10 @@ pub(crate) fn prune_repository( .into_par_iter() .try_for_each(|pack| -> RusticResult<_> { match pack.to_do { - PackToDo::Undecided => return Err(CommandErrorKind::NoDecision(pack.id).into()), + PackToDo::Undecided => { + return Err(CommandErrorKind::NoDecision(pack.id)) + .map_err(|_err| todo!("Error transition")) + } PackToDo::Keep => { // keep pack: add to new index let pack = pack.into_index_pack(); @@ -1528,8 +1537,7 @@ fn find_used_blobs( let p = pb.progress_counter("reading snapshots..."); let list: Vec<_> = be - .list(FileType::Snapshot) - .map_err(RusticErrorKind::Backend)? + .list(FileType::Snapshot)? .into_iter() .filter(|id| !ignore_snaps.contains(&SnapshotId::from(*id))) .collect(); diff --git a/crates/core/src/commands/repair/index.rs b/crates/core/src/commands/repair/index.rs index f2e8dc56..7c169f44 100644 --- a/crates/core/src/commands/repair/index.rs +++ b/crates/core/src/commands/repair/index.rs @@ -9,7 +9,7 @@ use crate::{ decrypt::{DecryptReadBackend, DecryptWriteBackend}, FileType, ReadBackend, WriteBackend, }, - error::{CommandErrorKind, RusticErrorKind, RusticResult}, + error::{ErrorKind, RusticResult}, index::{binarysorted::IndexCollector, indexer::Indexer, GlobalIndex}, progress::{Progress, ProgressBars}, repofile::{packfile::PackId, IndexFile, IndexPack, PackHeader, PackHeaderRef}, @@ -44,7 +44,10 @@ pub(crate) fn repair_index( dry_run: bool, ) -> RusticResult<()> { if repo.config().append_only == Some(true) { - return Err(CommandErrorKind::NotAllowedWithAppendOnly("index repair".to_string()).into()); + return Err(CommandErrorKind::NotAllowedWithAppendOnly( + "index repair".to_string(), + )) + .map_err(|_err| todo!("Error transition")); } let be = repo.dbe(); @@ -60,8 +63,7 @@ pub(crate) fn repair_index( if !new_index.packs.is_empty() || !new_index.packs_to_delete.is_empty() { _ = be.save_file(&new_index)?; } - be.remove(FileType::Index, &index_id, true) - .map_err(RusticErrorKind::Backend)?; + be.remove(FileType::Index, &index_id, true)?; } (false, _) => {} // nothing to do } @@ -77,7 +79,7 @@ pub(crate) fn repair_index( pack_read_header .len() .try_into() - .map_err(CommandErrorKind::ConversionFromIntFailed)?, + .map_err(|_err| todo!("Error transition"))?, ); for (id, size_hint, packsize) in pack_read_header { debug!("reading pack {id}..."); @@ -115,8 +117,7 @@ impl PackChecker { let be = repo.dbe(); let p = repo.pb.progress_spinner("listing packs..."); let packs: HashMap<_, _> = be - .list_with_size(FileType::Pack) - .map_err(RusticErrorKind::Backend)? + .list_with_size(FileType::Pack)? .into_iter() .map(|(id, size)| (PackId::from(id), size)) .collect(); @@ -190,7 +191,7 @@ pub(crate) fn index_checked_from_collector( pack_read_header .len() .try_into() - .map_err(CommandErrorKind::ConversionFromIntFailed)?, + .map_err(|_err| todo!("Error transition"))?, ); let index_packs: Vec<_> = pack_read_header .into_iter() diff --git a/crates/core/src/commands/repair/snapshots.rs b/crates/core/src/commands/repair/snapshots.rs index ebf6509c..22400743 100644 --- a/crates/core/src/commands/repair/snapshots.rs +++ b/crates/core/src/commands/repair/snapshots.rs @@ -14,7 +14,7 @@ use crate::{ tree::{Tree, TreeId}, BlobId, BlobType, }, - error::{CommandErrorKind, RusticResult}, + error::RusticResult, index::{indexer::Indexer, ReadGlobalIndex, ReadIndex}, progress::ProgressBars, repofile::{snapshotfile::SnapshotId, SnapshotFile, StringList}, @@ -280,7 +280,7 @@ pub(crate) fn repair_tree( (Some(id), Changed::None) => Ok((Changed::None, id)), (_, c) => { // the tree has been changed => save it - let (chunk, new_id) = tree.serialize()?; + let (chunk, new_id) = tree.serialize().map_err(|_err| todo!("Error transition"))?; if !index.has_tree(&new_id) && !dry_run { packer.add(chunk.into(), BlobId::from(*new_id))?; } diff --git a/crates/core/src/commands/repoinfo.rs b/crates/core/src/commands/repoinfo.rs index e105e305..7aa8c95f 100644 --- a/crates/core/src/commands/repoinfo.rs +++ b/crates/core/src/commands/repoinfo.rs @@ -4,7 +4,7 @@ use serde_with::skip_serializing_none; use crate::{ backend::{decrypt::DecryptReadBackend, FileType, ReadBackend, ALL_FILE_TYPES}, blob::{BlobType, BlobTypeMap}, - error::{RusticErrorKind, RusticResult}, + error::RusticResult, index::IndexEntry, progress::{Progress, ProgressBars}, repofile::indexfile::{IndexFile, IndexPack}, @@ -191,7 +191,7 @@ pub struct RepoFileInfo { pub(crate) fn collect_file_info(be: &impl ReadBackend) -> RusticResult> { let mut files = Vec::with_capacity(ALL_FILE_TYPES.len()); for tpe in ALL_FILE_TYPES { - let list = be.list_with_size(tpe).map_err(RusticErrorKind::Backend)?; + let list = be.list_with_size(tpe)?; let count = list.len() as u64; let size = list.iter().map(|f| u64::from(f.1)).sum(); files.push(RepoFileInfo { tpe, count, size }); diff --git a/crates/core/src/commands/restore.rs b/crates/core/src/commands/restore.rs index 033f9785..fcbb863d 100644 --- a/crates/core/src/commands/restore.rs +++ b/crates/core/src/commands/restore.rs @@ -23,7 +23,7 @@ use crate::{ node::{Node, NodeType}, FileType, ReadBackend, }, - error::{CommandErrorKind, RusticResult}, + error::RusticResult, progress::{Progress, ProgressBars}, repofile::packfile::PackId, repository::{IndexedFull, IndexedTree, Open, Repository}, @@ -222,9 +222,11 @@ pub(crate) fn collect_and_prepare( stats.dirs.restore += 1; debug!("to restore: {path:?}"); if !dry_run { - dest.create_dir(path).map_err(|err| { - CommandErrorKind::ErrorCreating(path.clone(), Box::new(err)) - })?; + dest.create_dir(path) + .map_err(|err| { + CommandErrorKind::ErrorCreating(path.clone(), Box::new(err)) + }) + .map_err(|_err| todo!("Error transition"))?; } } } @@ -236,7 +238,8 @@ pub(crate) fn collect_and_prepare( .add_file(dest, node, path.clone(), repo, opts.verify_existing) .map_err(|err| { CommandErrorKind::ErrorCollecting(path.clone(), Box::new(err)) - })?, + }) + .map_err(|_err| todo!("Error transition"))?, ) { // Note that exists = false and Existing or Verified can happen if the file is changed between scanning the dir // and calling add_file. So we don't care about exists but trust add_file here. @@ -450,7 +453,8 @@ fn restore_contents( if *size == 0 { let path = &filenames[i]; dest.set_length(path, *size) - .map_err(|err| CommandErrorKind::ErrorSettingLength(path.clone(), Box::new(err)))?; + .map_err(|err| CommandErrorKind::ErrorSettingLength(path.clone(), Box::new(err))) + .map_err(|_err| todo!("Error transition"))?; } } @@ -494,7 +498,8 @@ fn restore_contents( let pool = ThreadPoolBuilder::new() .num_threads(constants::MAX_READER_THREADS_NUM) .build() - .map_err(CommandErrorKind::FromRayonError)?; + .map_err(CommandErrorKind::FromRayonError) + .map_err(|_err| todo!("Error transition"))?; pool.in_place_scope(|s| { for (pack, offset, length, from_file, name_dests) in blobs { let p = &p; @@ -544,6 +549,7 @@ fn restore_contents( Box::new(err), ) }) + .map_err(|_err| todo!("Error transition")) .unwrap(); sizes_guard[file_idx] = 0; } @@ -664,7 +670,8 @@ impl RestorePlan { if let Some(meta) = open_file .as_ref() .map(std::fs::File::metadata) - .transpose()? + .transpose() + .map_err(|_err| todo!("Error transition"))? { if meta.len() == 0 { // Empty file exists @@ -677,7 +684,8 @@ impl RestorePlan { if let Some(meta) = open_file .as_ref() .map(std::fs::File::metadata) - .transpose()? + .transpose() + .map_err(|_err| todo!("Error transition"))? { // TODO: This is the same logic as in backend/ignore.rs => consollidate! let mtime = meta @@ -706,8 +714,9 @@ impl RestorePlan { }; let length = bl.data_length(); - let usize_length = - usize::try_from(length).map_err(CommandErrorKind::ConversionFromIntFailed)?; + let usize_length = usize::try_from(length) + .map_err(CommandErrorKind::ConversionFromIntFailed) + .map_err(|_err| todo!("Error transition"))?; let matches = open_file .as_mut() diff --git a/crates/core/src/crypto/aespoly1305.rs b/crates/core/src/crypto/aespoly1305.rs index c068f9b1..5802d40c 100644 --- a/crates/core/src/crypto/aespoly1305.rs +++ b/crates/core/src/crypto/aespoly1305.rs @@ -4,7 +4,7 @@ use aes256ctr_poly1305aes::{ }; use rand::{thread_rng, RngCore}; -use crate::{crypto::CryptoKey, error::CryptoErrorKind, error::RusticResult}; +use crate::crypto::{CryptoErrorKind, CryptoKey, CryptoResult}; pub(crate) type Nonce = aead::Nonce; pub(crate) type AeadKey = aes256ctr_poly1305aes::Key; @@ -82,7 +82,7 @@ impl CryptoKey for Key { /// # Errors /// /// If the MAC couldn't be checked. - fn decrypt_data(&self, data: &[u8]) -> RusticResult> { + fn decrypt_data(&self, data: &[u8]) -> CryptoResult> { if data.len() < 16 { return Err(CryptoErrorKind::CryptoKeyTooShort)?; } @@ -90,7 +90,7 @@ impl CryptoKey for Key { let nonce = Nonce::from_slice(&data[0..16]); Aes256CtrPoly1305Aes::new(&self.0) .decrypt(nonce, &data[16..]) - .map_err(|err| CryptoErrorKind::DataDecryptionFailed(err).into()) + .map_err(|err| CryptoErrorKind::DataDecryptionFailed(err)) } /// Returns the encrypted+MACed data from the given data. @@ -102,7 +102,7 @@ impl CryptoKey for Key { /// # Errors /// /// If the data could not be encrypted. - fn encrypt_data(&self, data: &[u8]) -> RusticResult> { + fn encrypt_data(&self, data: &[u8]) -> CryptoResult> { let mut nonce = Nonce::default(); thread_rng().fill_bytes(&mut nonce); diff --git a/crates/core/src/id.rs b/crates/core/src/id.rs index 6f9c59e5..6f1de2eb 100644 --- a/crates/core/src/id.rs +++ b/crates/core/src/id.rs @@ -7,7 +7,10 @@ use derive_more::{Constructor, Display}; use rand::{thread_rng, RngCore}; use serde_derive::{Deserialize, Serialize}; -use crate::{crypto::hasher::hash, error::IdErrorKind, RusticError, RusticResult}; +use crate::{ + crypto::hasher::hash, + error::{RusticError, ErrorKind, RusticResult}, +}; pub(super) mod constants { /// The length of the hash in bytes @@ -40,6 +43,14 @@ macro_rules! define_new_id_struct { )] #[serde(transparent)] pub struct $a($crate::Id); + + impl $a { + /// impl into_inner + #[must_use] + pub fn into_inner(self) -> $crate::Id { + self.0 + } + } }; } @@ -74,7 +85,12 @@ impl FromStr for Id { type Err = RusticError; fn from_str(s: &str) -> Result { let mut id = Self::default(); - hex::decode_to_slice(s, &mut id.0).map_err(IdErrorKind::HexError)?; + hex::decode_to_slice(s, &mut id.0).map_err(|err| { + RusticError::new(ErrorKind::Parsing, + format!("Failed to decode hex string into Id. The string must be a valid hexadecimal string: {s}") + ).source(err.into()) + })?; + Ok(id) } } diff --git a/crates/core/src/index.rs b/crates/core/src/index.rs index 42e9b901..3ab84bfd 100644 --- a/crates/core/src/index.rs +++ b/crates/core/src/index.rs @@ -4,7 +4,7 @@ use bytes::Bytes; use derive_more::Constructor; use crate::{ - backend::{decrypt::DecryptReadBackend, FileType}, + backend::{decrypt::DecryptReadBackend, CryptBackendErrorKind, FileType}, blob::{tree::TreeId, BlobId, BlobType, DataId}, error::RusticResult, index::binarysorted::{Index, IndexCollector, IndexType}, @@ -13,7 +13,6 @@ use crate::{ indexfile::{IndexBlob, IndexFile}, packfile::PackId, }, - RusticResult, }; pub(crate) mod binarysorted; @@ -203,7 +202,7 @@ pub trait ReadIndex { id: &BlobId, ) -> RusticResult { self.get_id(tpe, id).map_or_else( - || Err(IndexErrorKind::BlobInIndexNotFound.into()), + || Err(IndexErrorKind::BlobInIndexNotFound).map_err(|_err| todo!("Error transition")), |ie| ie.read_data(be), ) } diff --git a/crates/core/src/repofile/configfile.rs b/crates/core/src/repofile/configfile.rs index d5aa2d8b..8a19899f 100644 --- a/crates/core/src/repofile/configfile.rs +++ b/crates/core/src/repofile/configfile.rs @@ -1,9 +1,11 @@ +use std::num::ParseIntError; + use serde_derive::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::{ - backend::FileType, blob::BlobType, define_new_id_struct, error::ConfigFileErrorKind, - impl_repofile, repofile::RepoFile, RusticResult, + backend::FileType, blob::BlobType, define_new_id_struct, error::RusticResult, impl_repofile, + repofile::RepoFile, }; /// [`ConfigFileErrorKind`] describes the errors that can be returned for `ConfigFile`s @@ -153,10 +155,14 @@ impl ConfigFile { /// /// * [`ConfigFileErrorKind::ParsingFailedForPolynomial`] - If the polynomial could not be parsed /// - /// [`ConfigFileErrorKind::ParsingFailedForPolynomial`]: crate::error::ConfigFileErrorKind::ParsingFailedForPolynomial + /// [`ConfigFileErrorKind::ParsingFailedForPolynomial`]: ConfigFileErrorKind::ParsingFailedForPolynomial pub fn poly(&self) -> RusticResult { Ok(u64::from_str_radix(&self.chunker_polynomial, 16) - .map_err(ConfigFileErrorKind::ParsingFailedForPolynomial)?) + .map_err(|err| ConfigFileErrorKind::ParsingFailedForPolynomial { + polynomial: self.chunker_polynomial.clone(), + source: err, + }) + .map_err(|_err| todo!("Error transition"))?) } /// Get the compression level @@ -165,17 +171,21 @@ impl ConfigFile { /// /// * [`ConfigFileErrorKind::ConfigVersionNotSupported`] - If the version is not supported /// - /// [`ConfigFileErrorKind::ConfigVersionNotSupported`]: crate::error::ConfigFileErrorKind::ConfigVersionNotSupported + /// [`ConfigFileErrorKind::ConfigVersionNotSupported`]: ConfigFileErrorKind::ConfigVersionNotSupported pub fn zstd(&self) -> RusticResult> { match (self.version, self.compression) { (1, _) | (2, Some(0)) => Ok(None), (2, None) => Ok(Some(0)), // use default (=0) zstd compression (2, Some(c)) => Ok(Some(c)), - _ => Err(ConfigFileErrorKind::ConfigVersionNotSupported.into()), + _ => Err(ConfigFileErrorKind::ConfigVersionNotSupported { + version: self.version, + compression: self.compression, + }) + .map_err(|_err| todo!("Error transition")), } } - /// Get wheter an extra verification (decompressing/decrypting data before writing to the repository) should be performed. + /// Get whether an extra verification (decompressing/decrypting data before writing to the repository) should be performed. #[must_use] pub fn extra_verify(&self) -> bool { self.extra_verify.unwrap_or(true) // default is to do the extra check diff --git a/crates/core/src/repofile/keyfile.rs b/crates/core/src/repofile/keyfile.rs index 037d9fef..0abcf47c 100644 --- a/crates/core/src/repofile/keyfile.rs +++ b/crates/core/src/repofile/keyfile.rs @@ -6,9 +6,9 @@ use serde_with::{base64::Base64, serde_as, skip_serializing_none}; use crate::{ backend::{FileType, ReadBackend}, - crypto::{aespoly1305::Key, CryptoKey}, - error::{CryptoErrorKind, KeyFileErrorKind, RusticErrorKind, RusticResult}, - impl_repoid, RusticError, + crypto::{aespoly1305::Key, CryptoErrorKind, CryptoKey}, + error::RusticResult, + impl_repoid, }; /// [`KeyFileErrorKind`] describes the errors that can be returned for `KeyFile`s @@ -117,12 +117,19 @@ impl KeyFile { /// [`KeyFileErrorKind::InvalidSCryptParameters`]: crate::error::KeyFileErrorKind::InvalidSCryptParameters /// [`KeyFileErrorKind::OutputLengthInvalid`]: crate::error::KeyFileErrorKind::OutputLengthInvalid pub fn kdf_key(&self, passwd: &impl AsRef<[u8]>) -> RusticResult { - let params = Params::new(log_2(self.n)?, self.r, self.p, Params::RECOMMENDED_LEN) - .map_err(KeyFileErrorKind::InvalidSCryptParameters)?; + let params = Params::new( + log_2(self.n).map_err(|_err| todo!("Error transition"))?, + self.r, + self.p, + Params::RECOMMENDED_LEN, + ) + .map_err(KeyFileErrorKind::InvalidSCryptParameters) + .map_err(|_err| todo!("Error transition"))?; let mut key = [0; 64]; scrypt::scrypt(passwd.as_ref(), &self.salt, ¶ms, &mut key) - .map_err(KeyFileErrorKind::OutputLengthInvalid)?; + .map_err(KeyFileErrorKind::OutputLengthInvalid) + .map_err(|_err| todo!("Error transition"))?; Ok(Key::from_slice(&key)) } @@ -144,9 +151,17 @@ impl KeyFile { /// /// [`KeyFileErrorKind::DeserializingFromSliceFailed`]: crate::error::KeyFileErrorKind::DeserializingFromSliceFailed pub fn key_from_data(&self, key: &Key) -> RusticResult { - let dec_data = key.decrypt_data(&self.data)?; + let dec_data = key + .decrypt_data(&self.data) + .map_err(|err| KeyFileErrorKind::CouldNotGetKeyFromDecryptData { + key: key.clone(), + source: err, + }) + .map_err(|_err| todo!("Error transition"))?; + Ok(serde_json::from_slice::(&dec_data) - .map_err(KeyFileErrorKind::DeserializingFromSliceFailed)? + .map_err(|err| KeyFileErrorKind::DeserializingMasterKeyFromSliceFailed { source: err }) + .map_err(|_err| todo!("Error transition"))? .key()) } @@ -205,13 +220,17 @@ impl KeyFile { let mut key = [0; 64]; scrypt::scrypt(passwd.as_ref(), &salt, ¶ms, &mut key) - .map_err(KeyFileErrorKind::OutputLengthInvalid)?; + .map_err(KeyFileErrorKind::OutputLengthInvalid) + .map_err(|_err| todo!("Error transition"))?; let key = Key::from_slice(&key); - let data = key.encrypt_data( - &serde_json::to_vec(&masterkey) - .map_err(KeyFileErrorKind::CouldNotSerializeAsJsonByteVector)?, - )?; + let data = key + .encrypt_data( + &serde_json::to_vec(&masterkey) + .map_err(KeyFileErrorKind::CouldNotSerializeAsJsonByteVector) + .map_err(|_err| todo!("Error transition"))?, + ) + .map_err(|_err| todo!("Error transition"))?; Ok(Self { hostname, @@ -241,13 +260,16 @@ impl KeyFile { /// /// The [`KeyFile`] read from the backend fn from_backend(be: &B, id: &KeyId) -> RusticResult { - let data = be - .read_full(FileType::Key, id) - .map_err(RusticErrorKind::Backend)?; - Ok( - serde_json::from_slice(&data) - .map_err(KeyFileErrorKind::DeserializingFromSliceFailed)?, - ) + let data = be.read_full(FileType::Key, id)?; + + Ok(serde_json::from_slice(&data) + .map_err( + |err| KeyFileErrorKind::DeserializingFromSliceForKeyIdFailed { + key_id: id.clone(), + source: err, + }, + ) + .map_err(|_err| todo!("Error transition"))?) } } @@ -266,12 +288,21 @@ impl KeyFile { /// The logarithm to base 2 of the given number /// /// [`KeyFileErrorKind::ConversionFromU32ToU8Failed`]: crate::error::KeyFileErrorKind::ConversionFromU32ToU8Failed -fn log_2(x: u32) -> RusticResult { +fn log_2(x: u32) -> KeyFileResult { assert!(x > 0); - Ok(u8::try_from(constants::num_bits::()) - .map_err(KeyFileErrorKind::ConversionFromU32ToU8Failed)? - - u8::try_from(x.leading_zeros()).map_err(KeyFileErrorKind::ConversionFromU32ToU8Failed)? - - 1) + Ok(u8::try_from(constants::num_bits::()).map_err(|err| { + KeyFileErrorKind::ConversionFailed { + from: "usize", + to: "u8", + x, + source: err, + } + })? - u8::try_from(x.leading_zeros()).map_err(|err| KeyFileErrorKind::ConversionFailed { + from: "u32", + to: "u8", + x, + source: err, + })? - 1) } /// The mac of a [`Key`] @@ -372,15 +403,15 @@ pub(crate) fn find_key_in_backend( if let Some(id) = hint { key_from_backend(be, id, passwd) } else { - for id in be.list(FileType::Key).map_err(RusticErrorKind::Backend)? { + for id in be.list(FileType::Key)? { match key_from_backend(be, &id.into(), passwd) { Ok(key) => return Ok(key), - Err(RusticError(RusticErrorKind::Crypto( - CryptoErrorKind::DataDecryptionFailed(_), - ))) => continue, + // TODO: We get a RusticError here and we need to determine, if we have a WrongKey error + // TODO: We should probably implement something for that on RusticError or use a variant for this + Err(KeyFileErrorKind::DataDecryptionFailed(_)) => continue, err => return err, } } - Err(KeyFileErrorKind::NoSuitableKeyFound.into()) + Err(KeyFileErrorKind::NoSuitableKeyFound).map_err(|_err| todo!("Error transition")) } } diff --git a/crates/core/src/repofile/packfile.rs b/crates/core/src/repofile/packfile.rs index b3ae670e..741b7a24 100644 --- a/crates/core/src/repofile/packfile.rs +++ b/crates/core/src/repofile/packfile.rs @@ -6,11 +6,10 @@ use log::trace; use crate::{ backend::{decrypt::DecryptReadBackend, FileType}, blob::BlobType, - error::{PackFileErrorKind, RusticErrorKind}, + error::RusticResult, id::Id, impl_repoid, repofile::indexfile::{IndexBlob, IndexPack}, - RusticResult, }; /// [`PackFileErrorKind`] describes the errors that can be returned for `PackFile`s @@ -81,7 +80,7 @@ impl PackHeaderLength { /// * [`PackFileErrorKind::ReadingBinaryRepresentationFailed`] - If reading the binary representation failed /// /// [`PackFileErrorKind::ReadingBinaryRepresentationFailed`]: crate::error::PackFileErrorKind::ReadingBinaryRepresentationFailed - pub(crate) fn from_binary(data: &[u8]) -> RusticResult { + pub(crate) fn from_binary(data: &[u8]) -> PackFileResult { let mut reader = Cursor::new(data); Ok( Self::read(&mut reader) @@ -96,7 +95,7 @@ impl PackHeaderLength { /// * [`PackFileErrorKind::WritingBinaryRepresentationFailed`] - If writing the binary representation failed /// /// [`PackFileErrorKind::WritingBinaryRepresentationFailed`]: crate::error::PackFileErrorKind::WritingBinaryRepresentationFailed - pub(crate) fn to_binary(self) -> RusticResult> { + pub(crate) fn to_binary(self) -> PackFileResult> { let mut writer = Cursor::new(Vec::with_capacity(4)); self.write(&mut writer) .map_err(PackFileErrorKind::WritingBinaryRepresentationFailed)?; @@ -142,7 +141,7 @@ pub enum HeaderEntry { CompTree { /// Lengths within a packfile len: u32, - /// Raw blob length withou compression/encryption + /// Raw blob length without compression/encryption len_data: u32, /// Id of compressed tree blob id: Id, @@ -247,7 +246,7 @@ impl PackHeader { /// * [`PackFileErrorKind::ReadingBinaryRepresentationFailed`] - If reading the binary representation failed /// /// [`PackFileErrorKind::ReadingBinaryRepresentationFailed`]: crate::error::PackFileErrorKind::ReadingBinaryRepresentationFailed - pub(crate) fn from_binary(pack: &[u8]) -> RusticResult { + pub(crate) fn from_binary(pack: &[u8]) -> PackFileResult { let mut reader = Cursor::new(pack); let mut offset = 0; let mut blobs = Vec::new(); @@ -299,21 +298,20 @@ impl PackHeader { // read (guessed) header + length field let read_size = size_guess + constants::LENGTH_LEN; let offset = pack_size - read_size; - let mut data = be - .read_partial(FileType::Pack, &id, false, offset, read_size) - .map_err(RusticErrorKind::Backend)?; + let mut data = be.read_partial(FileType::Pack, &id, false, offset, read_size)?; // get header length from the file - let size_real = - PackHeaderLength::from_binary(&data.split_off(size_guess as usize))?.to_u32(); + let size_real = PackHeaderLength::from_binary(&data.split_off(size_guess as usize)) + .map_err(|_err| todo!("Error transition"))? + .to_u32(); trace!("header size: {size_real}"); if size_real + constants::LENGTH_LEN > pack_size { return Err(PackFileErrorKind::HeaderLengthTooLarge { size_real, pack_size, - } - .into()); + }) + .map_err(|_err| todo!("Error transition")); } // now read the header @@ -323,18 +321,18 @@ impl PackHeader { } else { // size_guess was too small; we have to read again let offset = pack_size - size_real - constants::LENGTH_LEN; - be.read_partial(FileType::Pack, &id, false, offset, size_real) - .map_err(RusticErrorKind::Backend)? + be.read_partial(FileType::Pack, &id, false, offset, size_real)? }; - let header = Self::from_binary(&be.decrypt(&data)?)?; + let header = + Self::from_binary(&be.decrypt(&data)?).map_err(|_err| todo!("Error transition"))?; if header.size() != size_real { return Err(PackFileErrorKind::HeaderLengthDoesNotMatchHeaderContents { size_real, size_computed: header.size(), - } - .into()); + }) + .map_err(|_err| todo!("Error transition")); } if header.pack_size() != pack_size { @@ -342,9 +340,9 @@ impl PackHeader { PackFileErrorKind::HeaderPackSizeComputedDoesNotMatchRealPackFile { size_real: pack_size, size_computed: header.pack_size(), - } - .into(), - ); + }, + ) + .map_err(|_err| todo!("Error transition")); } Ok(header) @@ -408,7 +406,7 @@ impl<'a> PackHeaderRef<'a> { /// * [`PackFileErrorKind::WritingBinaryRepresentationFailed`] - If writing the binary representation failed /// /// [`PackFileErrorKind::WritingBinaryRepresentationFailed`]: crate::error::PackFileErrorKind::WritingBinaryRepresentationFailed - pub(crate) fn to_binary(&self) -> RusticResult> { + pub(crate) fn to_binary(&self) -> PackFileResult> { let mut writer = Cursor::new(Vec::with_capacity(self.pack_size() as usize)); // collect header entries for blob in self.0 { diff --git a/crates/core/src/repofile/snapshotfile.rs b/crates/core/src/repofile/snapshotfile.rs index 17d18883..db047454 100644 --- a/crates/core/src/repofile/snapshotfile.rs +++ b/crates/core/src/repofile/snapshotfile.rs @@ -1,12 +1,13 @@ use std::{ cmp::Ordering, collections::{BTreeMap, BTreeSet}, + ffi::OsString, fmt::{self, Display}, path::{Path, PathBuf}, str::FromStr, }; -use chrono::{DateTime, Duration, Local}; +use chrono::{DateTime, Duration, Local, OutOfRangeError}; use derivative::Derivative; use derive_setters::Setters; use dunce::canonicalize; @@ -20,7 +21,7 @@ use serde_with::{serde_as, skip_serializing_none, DisplayFromStr}; use crate::{ backend::{decrypt::DecryptReadBackend, FileType, FindInBackend}, blob::tree::TreeId, - error::{RusticError, RusticErrorKind, RusticResult, SnapshotFileErrorKind}, + error::RusticResult, impl_repofile, progress::Progress, repofile::RepoFile, @@ -150,7 +151,8 @@ impl SnapshotOptions { /// /// [`SnapshotFileErrorKind::NonUnicodeTag`]: crate::error::SnapshotFileErrorKind::NonUnicodeTag pub fn add_tags(mut self, tag: &str) -> RusticResult { - self.tags.push(StringList::from_str(tag)?); + self.tags + .push(StringList::from_str(tag).map_err(|_err| todo!("Error transition"))?); Ok(self) } @@ -267,7 +269,7 @@ impl SnapshotSummary { /// * [`SnapshotFileErrorKind::OutOfRange`] - If the time is not in the range of `Local::now()` /// /// [`SnapshotFileErrorKind::OutOfRange`]: crate::error::SnapshotFileErrorKind::OutOfRange - pub(crate) fn finalize(&mut self, snap_time: DateTime) -> RusticResult<()> { + pub(crate) fn finalize(&mut self, snap_time: DateTime) -> SnapshotFileResult<()> { let end_time = Local::now(); self.backup_duration = (end_time - self.backup_start) .to_std() @@ -405,7 +407,8 @@ impl SnapshotFile { let hostname = gethostname(); hostname .to_str() - .ok_or_else(|| SnapshotFileErrorKind::NonUnicodeHostname(hostname.clone()))? + .ok_or_else(|| SnapshotFileErrorKind::NonUnicodeHostname(hostname.clone())) + .map_err(|_err| todo!("Error transition"))? .to_string() }; @@ -414,7 +417,9 @@ impl SnapshotFile { let delete = match (opts.delete_never, opts.delete_after) { (true, _) => DeleteOption::Never, (_, Some(d)) => DeleteOption::After( - time + Duration::from_std(*d).map_err(SnapshotFileErrorKind::OutOfRange)?, + time + Duration::from_std(*d) + .map_err(SnapshotFileErrorKind::OutOfRange) + .map_err(|_err| todo!("Error transition"))?, ), (false, None) => DeleteOption::NotSet, }; @@ -446,7 +451,8 @@ impl SnapshotFile { if let Some(ref file) = opts.description_from { snap.description = Some( std::fs::read_to_string(file) - .map_err(SnapshotFileErrorKind::ReadingDescriptionFailed)?, + .map_err(SnapshotFileErrorKind::ReadingDescriptionFailed) + .map_err(|_err| todo!("Error transition"))?, ); } @@ -544,7 +550,9 @@ impl SnapshotFile { } } p.finish(); - latest.ok_or_else(|| SnapshotFileErrorKind::NoSnapshotsFound.into()) + latest + .ok_or_else(|| SnapshotFileErrorKind::NoSnapshotsFound) + .map_err(|_err| todo!("Error transition")) } /// Get a [`SnapshotFile`] from the backend by (part of the) id @@ -784,9 +792,7 @@ impl SnapshotFile { B: DecryptReadBackend, F: FnMut(&Self) -> bool, { - let ids = be - .list(FileType::Snapshot) - .map_err(RusticErrorKind::Backend)?; + let ids = be.list(FileType::Snapshot)?; Self::fill_missing(be, current, &ids, filter, p) } @@ -978,8 +984,8 @@ impl Default for SnapshotGroupCriterion { } impl FromStr for SnapshotGroupCriterion { - type Err = RusticError; - fn from_str(s: &str) -> RusticResult { + type Err = SnapshotFileErrorKind; + fn from_str(s: &str) -> SnapshotFileResult { let mut crit = Self::new(); for val in s.split(',') { match val { @@ -1084,8 +1090,8 @@ impl SnapshotGroup { pub struct StringList(pub(crate) BTreeSet); impl FromStr for StringList { - type Err = RusticError; - fn from_str(s: &str) -> RusticResult { + type Err = SnapshotFileErrorKind; + fn from_str(s: &str) -> SnapshotFileResult { Ok(Self(s.split(',').map(ToString::to_string).collect())) } } @@ -1169,7 +1175,7 @@ impl StringList { /// * [`SnapshotFileErrorKind::NonUnicodePath`] - If a path is not valid unicode /// /// [`SnapshotFileErrorKind::NonUnicodePath`]: crate::error::SnapshotFileErrorKind::NonUnicodePath - pub(crate) fn set_paths>(&mut self, paths: &[T]) -> RusticResult<()> { + pub(crate) fn set_paths>(&mut self, paths: &[T]) -> SnapshotFileResult<()> { self.0 = paths .iter() .map(|p| { @@ -1178,7 +1184,7 @@ impl StringList { .ok_or_else(|| SnapshotFileErrorKind::NonUnicodePath(p.as_ref().to_path_buf()))? .to_string()) }) - .collect::>>()?; + .collect::>>()?; Ok(()) } @@ -1280,7 +1286,7 @@ impl PathList { /// /// [`SnapshotFileErrorKind::RemovingDotsFromPathFailed`]: crate::error::SnapshotFileErrorKind::RemovingDotsFromPathFailed /// [`SnapshotFileErrorKind::CanonicalizingPathFailed`]: crate::error::SnapshotFileErrorKind::CanonicalizingPathFailed - pub fn sanitize(mut self) -> RusticResult { + pub fn sanitize(mut self) -> SnapshotFileResult { for path in &mut self.0 { *path = sanitize_dot(path)?; } @@ -1317,7 +1323,7 @@ impl PathList { } // helper function to sanitize paths containing dots -fn sanitize_dot(path: &Path) -> RusticResult { +fn sanitize_dot(path: &Path) -> SnapshotFileResult { if path == Path::new(".") || path == Path::new("./") { return Ok(PathBuf::from(".")); } diff --git a/crates/core/src/repository.rs b/crates/core/src/repository.rs index 9504dff4..9d77bd59 100644 --- a/crates/core/src/repository.rs +++ b/crates/core/src/repository.rs @@ -9,7 +9,10 @@ use std::{ io::{BufRead, BufReader, Write}, path::{Path, PathBuf}, process::{Command, Stdio}, - sync::Arc, + sync::{ + atomic::{AtomicBool, Ordering as AtomicOrdering}, + Arc, + }, }; use bytes::Bytes; @@ -56,14 +59,14 @@ use crate::{ progress::{NoProgressBars, Progress, ProgressBars}, repofile::{ configfile::ConfigId, - keyfile::find_key_in_backend, + keyfile::{find_key_in_backend, KeyFileErrorKind}, packfile::PackId, snapshotfile::{SnapshotGroup, SnapshotGroupCriterion, SnapshotId}, ConfigFile, KeyId, PathList, RepoFile, RepoId, SnapshotFile, SnapshotSummary, Tree, }, repository::warm_up::{warm_up, warm_up_wait}, vfs::OpenFile, - RepositoryBackends, RusticResult, + RepositoryBackends, }; #[cfg(feature = "clap")] @@ -168,27 +171,27 @@ impl RepositoryOptions { /// /// # Errors /// - /// * [`RepositoryErrorKind::OpeningPasswordFileFailed`] - If opening the password file failed - /// * [`RepositoryErrorKind::ReadingPasswordFromReaderFailed`] - If reading the password failed - /// * [`RepositoryErrorKind::FromSplitError`] - If splitting the password command failed - /// * [`RepositoryErrorKind::PasswordCommandExecutionFailed`] - If executing the password command failed - /// * [`RepositoryErrorKind::ReadingPasswordFromCommandFailed`] - If reading the password from the command failed + /// * [`RusticErrorKind::OpeningPasswordFileFailed`] - If opening the password file failed + /// * [`RusticErrorKind::ReadingPasswordFromReaderFailed`] - If reading the password failed + /// * [`RusticErrorKind::FromSplitError`] - If splitting the password command failed + /// * [`RusticErrorKind::PasswordCommandExecutionFailed`] - If executing the password command failed + /// * [`RusticErrorKind::ReadingPasswordFromCommandFailed`] - If reading the password from the command failed /// /// # Returns /// /// The password or `None` if no password is given /// - /// [`RepositoryErrorKind::OpeningPasswordFileFailed`]: crate::error::RepositoryErrorKind::OpeningPasswordFileFailed - /// [`RepositoryErrorKind::ReadingPasswordFromReaderFailed`]: crate::error::RepositoryErrorKind::ReadingPasswordFromReaderFailed - /// [`RepositoryErrorKind::FromSplitError`]: crate::error::RepositoryErrorKind::FromSplitError - /// [`RepositoryErrorKind::PasswordCommandExecutionFailed`]: crate::error::RepositoryErrorKind::PasswordCommandExecutionFailed - /// [`RepositoryErrorKind::ReadingPasswordFromCommandFailed`]: crate::error::RepositoryErrorKind::ReadingPasswordFromCommandFailed + /// [`RusticErrorKind::OpeningPasswordFileFailed`]: crate::error::RusticErrorKind::OpeningPasswordFileFailed + /// [`RusticErrorKind::ReadingPasswordFromReaderFailed`]: crate::error::RusticErrorKind::ReadingPasswordFromReaderFailed + /// [`RusticErrorKind::FromSplitError`]: crate::error::RusticErrorKind::FromSplitError + /// [`RusticErrorKind::PasswordCommandExecutionFailed`]: crate::error::RusticErrorKind::PasswordCommandExecutionFailed + /// [`RusticErrorKind::ReadingPasswordFromCommandFailed`]: crate::error::RusticErrorKind::ReadingPasswordFromCommandFailed pub fn evaluate_password(&self) -> RusticResult> { match (&self.password, &self.password_file, &self.password_command) { (Some(pwd), _, _) => Ok(Some(pwd.clone())), (_, Some(file), _) => { let mut file = BufReader::new( - File::open(file).map_err(RepositoryErrorKind::OpeningPasswordFileFailed)?, + File::open(file).map_err(ErrorKind::OpeningPasswordFileFailed)?, ); Ok(Some(read_password_from_reader(&mut file)?)) } @@ -203,7 +206,7 @@ impl RepositoryOptions { Ok(process) => process, Err(err) => { error!("password-command could not be executed: {}", err); - return Err(RepositoryErrorKind::PasswordCommandExecutionFailed.into()); + return Err(ErrorKind::PasswordCommandExecutionFailed.into()); } }; @@ -211,7 +214,7 @@ impl RepositoryOptions { Ok(output) => output, Err(err) => { error!("error reading output from password-command: {}", err); - return Err(RepositoryErrorKind::ReadingPasswordFromCommandFailed.into()); + return Err(ErrorKind::ReadingPasswordFromCommandFailed.into()); } }; @@ -222,15 +225,13 @@ impl RepositoryOptions { None => "was terminated".into(), }; error!("password-command {s}"); - return Err(RepositoryErrorKind::PasswordCommandExecutionFailed.into()); + return Err(ErrorKind::PasswordCommandExecutionFailed.into()); } let mut pwd = BufReader::new(&*output.stdout); Ok(Some(match read_password_from_reader(&mut pwd) { Ok(val) => val, - Err(_) => { - return Err(RepositoryErrorKind::ReadingPasswordFromCommandFailed.into()) - } + Err(_) => return Err(ErrorKind::ReadingPasswordFromCommandFailed.into()), })) } (None, None, _) => Ok(None), @@ -246,14 +247,14 @@ impl RepositoryOptions { /// /// # Errors /// -/// * [`RepositoryErrorKind::ReadingPasswordFromReaderFailed`] - If reading the password failed +/// * [`RusticErrorKind::ReadingPasswordFromReaderFailed`] - If reading the password failed /// -/// [`RepositoryErrorKind::ReadingPasswordFromReaderFailed`]: crate::error::RepositoryErrorKind::ReadingPasswordFromReaderFailed +/// [`RusticErrorKind::ReadingPasswordFromReaderFailed`]: crate::error::RusticErrorKind::ReadingPasswordFromReaderFailed pub fn read_password_from_reader(file: &mut impl BufRead) -> RusticResult { let mut password = String::new(); _ = file .read_line(&mut password) - .map_err(RepositoryErrorKind::ReadingPasswordFromReaderFailed)?; + .map_err(ErrorKind::ReadingPasswordFromReaderFailed)?; // Remove the \n from the line if present if password.ends_with('\n') { @@ -309,8 +310,8 @@ impl Repository { /// /// # Errors /// - /// * [`RepositoryErrorKind::NoRepositoryGiven`] - If no repository is given - /// * [`RepositoryErrorKind::NoIDSpecified`] - If the warm-up command does not contain `%id` + /// * [`RusticErrorKind::NoRepositoryGiven`] - If no repository is given + /// * [`RusticErrorKind::NoIDSpecified`] - If the warm-up command does not contain `%id` /// * [`BackendAccessErrorKind::BackendLoadError`] - If the specified backend cannot be loaded, e.g. is not supported /// /// [`BackendAccessErrorKind::BackendLoadError`]: crate::error::BackendAccessErrorKind::BackendLoadError @@ -334,12 +335,12 @@ impl

Repository { /// /// # Errors /// - /// * [`RepositoryErrorKind::NoRepositoryGiven`] - If no repository is given - /// * [`RepositoryErrorKind::NoIDSpecified`] - If the warm-up command does not contain `%id` + /// * [`RusticErrorKind::NoRepositoryGiven`] - If no repository is given + /// * [`RusticErrorKind::NoIDSpecified`] - If the warm-up command does not contain `%id` /// * [`BackendAccessErrorKind::BackendLoadError`] - If the specified backend cannot be loaded, e.g. is not supported /// - /// [`RepositoryErrorKind::NoRepositoryGiven`]: crate::error::RepositoryErrorKind::NoRepositoryGiven - /// [`RepositoryErrorKind::NoIDSpecified`]: crate::error::RepositoryErrorKind::NoIDSpecified + /// [`RusticErrorKind::NoRepositoryGiven`]: crate::error::RusticErrorKind::NoRepositoryGiven + /// [`RusticErrorKind::NoIDSpecified`]: crate::error::RusticErrorKind::NoIDSpecified /// [`BackendAccessErrorKind::BackendLoadError`]: crate::error::BackendAccessErrorKind::BackendLoadError pub fn new_with_progress( opts: &RepositoryOptions, @@ -351,7 +352,7 @@ impl

Repository { if let Some(warm_up) = &opts.warm_up_command { if warm_up.args().iter().all(|c| !c.contains("%id")) { - return Err(RepositoryErrorKind::NoIDSpecified.into()); + return Err(ErrorKind::NoIDSpecified.into()); } info!("using warm-up command {warm_up}"); } @@ -383,21 +384,21 @@ impl Repository { /// /// # Errors /// - /// * [`RepositoryErrorKind::OpeningPasswordFileFailed`] - If opening the password file failed - /// * [`RepositoryErrorKind::ReadingPasswordFromReaderFailed`] - If reading the password failed - /// * [`RepositoryErrorKind::FromSplitError`] - If splitting the password command failed - /// * [`RepositoryErrorKind::PasswordCommandExecutionFailed`] - If parsing the password command failed - /// * [`RepositoryErrorKind::ReadingPasswordFromCommandFailed`] - If reading the password from the command failed + /// * [`RusticErrorKind::OpeningPasswordFileFailed`] - If opening the password file failed + /// * [`RusticErrorKind::ReadingPasswordFromReaderFailed`] - If reading the password failed + /// * [`RusticErrorKind::FromSplitError`] - If splitting the password command failed + /// * [`RusticErrorKind::PasswordCommandExecutionFailed`] - If parsing the password command failed + /// * [`RusticErrorKind::ReadingPasswordFromCommandFailed`] - If reading the password from the command failed /// /// # Returns /// /// The password or `None` if no password is given /// - /// [`RepositoryErrorKind::OpeningPasswordFileFailed`]: crate::error::RepositoryErrorKind::OpeningPasswordFileFailed - /// [`RepositoryErrorKind::ReadingPasswordFromReaderFailed`]: crate::error::RepositoryErrorKind::ReadingPasswordFromReaderFailed - /// [`RepositoryErrorKind::FromSplitError`]: crate::error::RepositoryErrorKind::FromSplitError - /// [`RepositoryErrorKind::PasswordCommandExecutionFailed`]: crate::error::RepositoryErrorKind::PasswordCommandExecutionFailed - /// [`RepositoryErrorKind::ReadingPasswordFromCommandFailed`]: crate::error::RepositoryErrorKind::ReadingPasswordFromCommandFailed + /// [`RusticErrorKind::OpeningPasswordFileFailed`]: crate::error::RusticErrorKind::OpeningPasswordFileFailed + /// [`RusticErrorKind::ReadingPasswordFromReaderFailed`]: crate::error::RusticErrorKind::ReadingPasswordFromReaderFailed + /// [`RusticErrorKind::FromSplitError`]: crate::error::RusticErrorKind::FromSplitError + /// [`RusticErrorKind::PasswordCommandExecutionFailed`]: crate::error::RusticErrorKind::PasswordCommandExecutionFailed + /// [`RusticErrorKind::ReadingPasswordFromCommandFailed`]: crate::error::RusticErrorKind::ReadingPasswordFromCommandFailed pub fn password(&self) -> RusticResult> { self.opts.evaluate_password() } @@ -406,25 +407,25 @@ impl Repository { /// /// # Errors /// - /// * [`RepositoryErrorKind::ListingRepositoryConfigFileFailed`] - If listing the repository config file failed - /// * [`RepositoryErrorKind::MoreThanOneRepositoryConfig`] - If there is more than one repository config file + /// * [`RusticErrorKind::ListingRepositoryConfigFileFailed`] - If listing the repository config file failed + /// * [`RusticErrorKind::MoreThanOneRepositoryConfig`] - If there is more than one repository config file /// /// # Returns /// /// The id of the config file or `None` if no config file is found /// - /// [`RepositoryErrorKind::ListingRepositoryConfigFileFailed`]: crate::error::RepositoryErrorKind::ListingRepositoryConfigFileFailed - /// [`RepositoryErrorKind::MoreThanOneRepositoryConfig`]: crate::error::RepositoryErrorKind::MoreThanOneRepositoryConfig + /// [`RusticErrorKind::ListingRepositoryConfigFileFailed`]: crate::error::RusticErrorKind::ListingRepositoryConfigFileFailed + /// [`RusticErrorKind::MoreThanOneRepositoryConfig`]: crate::error::RusticErrorKind::MoreThanOneRepositoryConfig pub fn config_id(&self) -> RusticResult> { let config_ids = self .be .list(FileType::Config) - .map_err(|_| RepositoryErrorKind::ListingRepositoryConfigFileFailed)?; + .map_err(|_| ErrorKind::ListingRepositoryConfigFileFailed)?; match config_ids.len() { 1 => Ok(Some(ConfigId::from(config_ids[0]))), 0 => Ok(None), - _ => Err(RepositoryErrorKind::MoreThanOneRepositoryConfig(self.name.clone()).into()), + _ => Err(ErrorKind::MoreThanOneRepositoryConfig(self.name.clone()).into()), } } @@ -434,39 +435,37 @@ impl Repository { /// /// # Errors /// - /// * [`RepositoryErrorKind::NoPasswordGiven`] - If no password is given - /// * [`RepositoryErrorKind::ReadingPasswordFromReaderFailed`] - If reading the password failed - /// * [`RepositoryErrorKind::OpeningPasswordFileFailed`] - If opening the password file failed - /// * [`RepositoryErrorKind::PasswordCommandExecutionFailed`] - If parsing the password command failed - /// * [`RepositoryErrorKind::ReadingPasswordFromCommandFailed`] - If reading the password from the command failed - /// * [`RepositoryErrorKind::FromSplitError`] - If splitting the password command failed - /// * [`RepositoryErrorKind::NoRepositoryConfigFound`] - If no repository config file is found - /// * [`RepositoryErrorKind::KeysDontMatchForRepositories`] - If the keys of the hot and cold backend don't match - /// * [`RepositoryErrorKind::IncorrectPassword`] - If the password is incorrect + /// * [`RusticErrorKind::NoPasswordGiven`] - If no password is given + /// * [`RusticErrorKind::ReadingPasswordFromReaderFailed`] - If reading the password failed + /// * [`RusticErrorKind::OpeningPasswordFileFailed`] - If opening the password file failed + /// * [`RusticErrorKind::PasswordCommandExecutionFailed`] - If parsing the password command failed + /// * [`RusticErrorKind::ReadingPasswordFromCommandFailed`] - If reading the password from the command failed + /// * [`RusticErrorKind::FromSplitError`] - If splitting the password command failed + /// * [`RusticErrorKind::NoRepositoryConfigFound`] - If no repository config file is found + /// * [`RusticErrorKind::KeysDontMatchForRepositories`] - If the keys of the hot and cold backend don't match + /// * [`RusticErrorKind::IncorrectPassword`] - If the password is incorrect /// * [`KeyFileErrorKind::NoSuitableKeyFound`] - If no suitable key is found - /// * [`RepositoryErrorKind::ListingRepositoryConfigFileFailed`] - If listing the repository config file failed - /// * [`RepositoryErrorKind::MoreThanOneRepositoryConfig`] - If there is more than one repository config file + /// * [`RusticErrorKind::ListingRepositoryConfigFileFailed`] - If listing the repository config file failed + /// * [`RusticErrorKind::MoreThanOneRepositoryConfig`] - If there is more than one repository config file /// /// # Returns /// /// The open repository /// - /// [`RepositoryErrorKind::NoPasswordGiven`]: crate::error::RepositoryErrorKind::NoPasswordGiven - /// [`RepositoryErrorKind::ReadingPasswordFromReaderFailed`]: crate::error::RepositoryErrorKind::ReadingPasswordFromReaderFailed - /// [`RepositoryErrorKind::OpeningPasswordFileFailed`]: crate::error::RepositoryErrorKind::OpeningPasswordFileFailed - /// [`RepositoryErrorKind::PasswordCommandExecutionFailed`]: crate::error::RepositoryErrorKind::PasswordCommandExecutionFailed - /// [`RepositoryErrorKind::ReadingPasswordFromCommandFailed`]: crate::error::RepositoryErrorKind::ReadingPasswordFromCommandFailed - /// [`RepositoryErrorKind::FromSplitError`]: crate::error::RepositoryErrorKind::FromSplitError - /// [`RepositoryErrorKind::NoRepositoryConfigFound`]: crate::error::RepositoryErrorKind::NoRepositoryConfigFound - /// [`RepositoryErrorKind::KeysDontMatchForRepositories`]: crate::error::RepositoryErrorKind::KeysDontMatchForRepositories - /// [`RepositoryErrorKind::IncorrectPassword`]: crate::error::RepositoryErrorKind::IncorrectPassword + /// [`RusticErrorKind::NoPasswordGiven`]: crate::error::RusticErrorKind::NoPasswordGiven + /// [`RusticErrorKind::ReadingPasswordFromReaderFailed`]: crate::error::RusticErrorKind::ReadingPasswordFromReaderFailed + /// [`RusticErrorKind::OpeningPasswordFileFailed`]: crate::error::RusticErrorKind::OpeningPasswordFileFailed + /// [`RusticErrorKind::PasswordCommandExecutionFailed`]: crate::error::RusticErrorKind::PasswordCommandExecutionFailed + /// [`RusticErrorKind::ReadingPasswordFromCommandFailed`]: crate::error::RusticErrorKind::ReadingPasswordFromCommandFailed + /// [`RusticErrorKind::FromSplitError`]: crate::error::RusticErrorKind::FromSplitError + /// [`RusticErrorKind::NoRepositoryConfigFound`]: crate::error::RusticErrorKind::NoRepositoryConfigFound + /// [`RusticErrorKind::KeysDontMatchForRepositories`]: crate::error::RusticErrorKind::KeysDontMatchForRepositories + /// [`RusticErrorKind::IncorrectPassword`]: crate::error::RusticErrorKind::IncorrectPassword /// [`KeyFileErrorKind::NoSuitableKeyFound`]: crate::error::KeyFileErrorKind::NoSuitableKeyFound - /// [`RepositoryErrorKind::ListingRepositoryConfigFileFailed`]: crate::error::RepositoryErrorKind::ListingRepositoryConfigFileFailed - /// [`RepositoryErrorKind::MoreThanOneRepositoryConfig`]: crate::error::RepositoryErrorKind::MoreThanOneRepositoryConfig + /// [`RusticErrorKind::ListingRepositoryConfigFileFailed`]: crate::error::RusticErrorKind::ListingRepositoryConfigFileFailed + /// [`RusticErrorKind::MoreThanOneRepositoryConfig`]: crate::error::RusticErrorKind::MoreThanOneRepositoryConfig pub fn open(self) -> RusticResult> { - let password = self - .password()? - .ok_or(RepositoryErrorKind::NoPasswordGiven)?; + let password = self.password()?.ok_or(ErrorKind::NoPasswordGiven)?; self.open_with_password(&password) } @@ -480,50 +479,40 @@ impl Repository { /// /// # Errors /// - /// * [`RepositoryErrorKind::NoRepositoryConfigFound`] - If no repository config file is found - /// * [`RepositoryErrorKind::KeysDontMatchForRepositories`] - If the keys of the hot and cold backend don't match - /// * [`RepositoryErrorKind::IncorrectPassword`] - If the password is incorrect + /// * [`RusticErrorKind::NoRepositoryConfigFound`] - If no repository config file is found + /// * [`RusticErrorKind::KeysDontMatchForRepositories`] - If the keys of the hot and cold backend don't match + /// * [`RusticErrorKind::IncorrectPassword`] - If the password is incorrect /// * [`KeyFileErrorKind::NoSuitableKeyFound`] - If no suitable key is found - /// * [`RepositoryErrorKind::ListingRepositoryConfigFileFailed`] - If listing the repository config file failed - /// * [`RepositoryErrorKind::MoreThanOneRepositoryConfig`] - If there is more than one repository config file + /// * [`RusticErrorKind::ListingRepositoryConfigFileFailed`] - If listing the repository config file failed + /// * [`RusticErrorKind::MoreThanOneRepositoryConfig`] - If there is more than one repository config file /// - /// [`RepositoryErrorKind::NoRepositoryConfigFound`]: crate::error::RepositoryErrorKind::NoRepositoryConfigFound - /// [`RepositoryErrorKind::KeysDontMatchForRepositories`]: crate::error::RepositoryErrorKind::KeysDontMatchForRepositories - /// [`RepositoryErrorKind::IncorrectPassword`]: crate::error::RepositoryErrorKind::IncorrectPassword + /// [`RusticErrorKind::NoRepositoryConfigFound`]: crate::error::RusticErrorKind::NoRepositoryConfigFound + /// [`RusticErrorKind::KeysDontMatchForRepositories`]: crate::error::RusticErrorKind::KeysDontMatchForRepositories + /// [`RusticErrorKind::IncorrectPassword`]: crate::error::RusticErrorKind::IncorrectPassword /// [`KeyFileErrorKind::NoSuitableKeyFound`]: crate::error::KeyFileErrorKind::NoSuitableKeyFound - /// [`RepositoryErrorKind::ListingRepositoryConfigFileFailed`]: crate::error::RepositoryErrorKind::ListingRepositoryConfigFileFailed - /// [`RepositoryErrorKind::MoreThanOneRepositoryConfig`]: crate::error::RepositoryErrorKind::MoreThanOneRepositoryConfig + /// [`RusticErrorKind::ListingRepositoryConfigFileFailed`]: crate::error::RusticErrorKind::ListingRepositoryConfigFileFailed + /// [`RusticErrorKind::MoreThanOneRepositoryConfig`]: crate::error::RusticErrorKind::MoreThanOneRepositoryConfig pub fn open_with_password(self, password: &str) -> RusticResult> { let config_id = self .config_id()? - .ok_or(RepositoryErrorKind::NoRepositoryConfigFound( - self.name.clone(), - ))?; + .ok_or(ErrorKind::NoRepositoryConfigFound(self.name.clone())) + .map_err(|_err| todo!("Error transition"))?; if let Some(be_hot) = &self.be_hot { - let mut keys = self - .be - .list_with_size(FileType::Key) - .map_err(RusticErrorKind::Backend)?; + let mut keys = self.be.list_with_size(FileType::Key)?; keys.sort_unstable_by_key(|key| key.0); - let mut hot_keys = be_hot - .list_with_size(FileType::Key) - .map_err(RusticErrorKind::Backend)?; + let mut hot_keys = be_hot.list_with_size(FileType::Key)?; hot_keys.sort_unstable_by_key(|key| key.0); if keys != hot_keys { - return Err(RepositoryErrorKind::KeysDontMatchForRepositories(self.name).into()); + return Err(ErrorKind::KeysDontMatchForRepositories(self.name).into()) + .map_err(|_err| todo!("Error transition")); } } - let key = find_key_in_backend(&self.be, &password, None).map_err(|err| { - match err.into_inner() { - RusticErrorKind::KeyFile(KeyFileErrorKind::NoSuitableKeyFound) => { - RepositoryErrorKind::IncorrectPassword.into() - } - err => err, - } - })?; + let key = find_key_in_backend(&self.be, &password, None)?; + info!("repository {}: password is correct.", self.name); + let dbe = DecryptBackend::new(self.be.clone(), key); let config: ConfigFile = dbe.get_file(&config_id)?; self.open_raw(key, config) @@ -544,27 +533,25 @@ impl Repository { /// /// # Errors /// - /// * [`RepositoryErrorKind::NoPasswordGiven`] - If no password is given - /// * [`RepositoryErrorKind::ReadingPasswordFromReaderFailed`] - If reading the password failed - /// * [`RepositoryErrorKind::OpeningPasswordFileFailed`] - If opening the password file failed - /// * [`RepositoryErrorKind::PasswordCommandExecutionFailed`] - If parsing the password command failed - /// * [`RepositoryErrorKind::ReadingPasswordFromCommandFailed`] - If reading the password from the command failed - /// * [`RepositoryErrorKind::FromSplitError`] - If splitting the password command failed - /// - /// [`RepositoryErrorKind::NoPasswordGiven`]: crate::error::RepositoryErrorKind::NoPasswordGiven - /// [`RepositoryErrorKind::ReadingPasswordFromReaderFailed`]: crate::error::RepositoryErrorKind::ReadingPasswordFromReaderFailed - /// [`RepositoryErrorKind::OpeningPasswordFileFailed`]: crate::error::RepositoryErrorKind::OpeningPasswordFileFailed - /// [`RepositoryErrorKind::PasswordCommandExecutionFailed`]: crate::error::RepositoryErrorKind::PasswordCommandExecutionFailed - /// [`RepositoryErrorKind::ReadingPasswordFromCommandFailed`]: crate::error::RepositoryErrorKind::ReadingPasswordFromCommandFailed - /// [`RepositoryErrorKind::FromSplitError`]: crate::error::RepositoryErrorKind::FromSplitError + /// * [`RusticErrorKind::NoPasswordGiven`] - If no password is given + /// * [`RusticErrorKind::ReadingPasswordFromReaderFailed`] - If reading the password failed + /// * [`RusticErrorKind::OpeningPasswordFileFailed`] - If opening the password file failed + /// * [`RusticErrorKind::PasswordCommandExecutionFailed`] - If parsing the password command failed + /// * [`RusticErrorKind::ReadingPasswordFromCommandFailed`] - If reading the password from the command failed + /// * [`RusticErrorKind::FromSplitError`] - If splitting the password command failed + /// + /// [`RusticErrorKind::NoPasswordGiven`]: crate::error::RusticErrorKind::NoPasswordGiven + /// [`RusticErrorKind::ReadingPasswordFromReaderFailed`]: crate::error::RusticErrorKind::ReadingPasswordFromReaderFailed + /// [`RusticErrorKind::OpeningPasswordFileFailed`]: crate::error::RusticErrorKind::OpeningPasswordFileFailed + /// [`RusticErrorKind::PasswordCommandExecutionFailed`]: crate::error::RusticErrorKind::PasswordCommandExecutionFailed + /// [`RusticErrorKind::ReadingPasswordFromCommandFailed`]: crate::error::RusticErrorKind::ReadingPasswordFromCommandFailed + /// [`RusticErrorKind::FromSplitError`]: crate::error::RusticErrorKind::FromSplitError pub fn init( self, key_opts: &KeyOptions, config_opts: &ConfigOptions, ) -> RusticResult> { - let password = self - .password()? - .ok_or(RepositoryErrorKind::NoPasswordGiven)?; + let password = self.password()?.ok_or(ErrorKind::NoPasswordGiven)?; self.init_with_password(&password, key_opts, config_opts) } @@ -584,13 +571,13 @@ impl Repository { /// /// # Errors /// - /// * [`RepositoryErrorKind::ConfigFileExists`] - If a config file already exists - /// * [`RepositoryErrorKind::ListingRepositoryConfigFileFailed`] - If listing the repository config file failed - /// * [`RepositoryErrorKind::MoreThanOneRepositoryConfig`] - If there is more than one repository config file + /// * [`RusticErrorKind::ConfigFileExists`] - If a config file already exists + /// * [`RusticErrorKind::ListingRepositoryConfigFileFailed`] - If listing the repository config file failed + /// * [`RusticErrorKind::MoreThanOneRepositoryConfig`] - If there is more than one repository config file /// - /// [`RepositoryErrorKind::ConfigFileExists`]: crate::error::RepositoryErrorKind::ConfigFileExists - /// [`RepositoryErrorKind::ListingRepositoryConfigFileFailed`]: crate::error::RepositoryErrorKind::ListingRepositoryConfigFileFailed - /// [`RepositoryErrorKind::MoreThanOneRepositoryConfig`]: crate::error::RepositoryErrorKind::MoreThanOneRepositoryConfig + /// [`RusticErrorKind::ConfigFileExists`]: crate::error::RusticErrorKind::ConfigFileExists + /// [`RusticErrorKind::ListingRepositoryConfigFileFailed`]: crate::error::RusticErrorKind::ListingRepositoryConfigFileFailed + /// [`RusticErrorKind::MoreThanOneRepositoryConfig`]: crate::error::RusticErrorKind::MoreThanOneRepositoryConfig pub fn init_with_password( self, pass: &str, @@ -598,9 +585,10 @@ impl Repository { config_opts: &ConfigOptions, ) -> RusticResult> { if self.config_id()?.is_some() { - return Err(RepositoryErrorKind::ConfigFileExists.into()); + return Err(ErrorKind::ConfigFileExists.into()); } let (key, config) = commands::init::init(&self, pass, key_opts, config_opts)?; + self.open_raw(key, config) } @@ -645,15 +633,15 @@ impl Repository { /// /// # Errors /// - /// * [`RepositoryErrorKind::HotRepositoryFlagMissing`] - If the config file has `is_hot` set to `true` but the repository is not hot - /// * [`RepositoryErrorKind::IsNotHotRepository`] - If the config file has `is_hot` set to `false` but the repository is hot + /// * [`RusticErrorKind::HotRepositoryFlagMissing`] - If the config file has `is_hot` set to `true` but the repository is not hot + /// * [`RusticErrorKind::IsNotHotRepository`] - If the config file has `is_hot` set to `false` but the repository is hot /// - /// [`RepositoryErrorKind::HotRepositoryFlagMissing`]: crate::error::RepositoryErrorKind::HotRepositoryFlagMissing - /// [`RepositoryErrorKind::IsNotHotRepository`]: crate::error::RepositoryErrorKind::IsNotHotRepository + /// [`RusticErrorKind::HotRepositoryFlagMissing`]: crate::error::RusticErrorKind::HotRepositoryFlagMissing + /// [`RusticErrorKind::IsNotHotRepository`]: crate::error::RusticErrorKind::IsNotHotRepository fn open_raw(mut self, key: Key, config: ConfigFile) -> RusticResult> { match (config.is_hot == Some(true), self.be_hot.is_some()) { - (true, false) => return Err(RepositoryErrorKind::HotRepositoryFlagMissing.into()), - (false, true) => return Err(RepositoryErrorKind::IsNotHotRepository.into()), + (true, false) => return Err(ErrorKind::HotRepositoryFlagMissing.into()), + (false, true) => return Err(ErrorKind::IsNotHotRepository.into()), _ => {} } @@ -694,12 +682,7 @@ impl Repository { /// // TODO: Document errors pub fn list(&self) -> RusticResult> { - Ok(self - .be - .list(T::TYPE) - .map_err(RusticErrorKind::Backend)? - .into_iter() - .map(Into::into)) + Ok(self.be.list(T::TYPE)?.into_iter().map(Into::into)) } } @@ -721,14 +704,14 @@ impl Repository { /// /// # Errors /// - /// * [`RepositoryErrorKind::FromSplitError`] - If the command could not be parsed. - /// * [`RepositoryErrorKind::FromThreadPoolbilderError`] - If the thread pool could not be created. + /// * [`RusticErrorKind::FromSplitError`] - If the command could not be parsed. + /// * [`RusticErrorKind::FromThreadPoolbilderError`] - If the thread pool could not be created. /// /// # Returns /// /// The result of the warm up pub fn warm_up(&self, packs: impl ExactSizeIterator) -> RusticResult<()> { - warm_up(self, packs) + warm_up(self, packs).map_err(|_err| todo!("Error transition")) } /// Warm up the given pack files and wait the configured waiting time. @@ -739,13 +722,13 @@ impl Repository { /// /// # Errors /// - /// * [`RepositoryErrorKind::FromSplitError`] - If the command could not be parsed. - /// * [`RepositoryErrorKind::FromThreadPoolbilderError`] - If the thread pool could not be created. + /// * [`RusticErrorKind::FromSplitError`] - If the command could not be parsed. + /// * [`RusticErrorKind::FromThreadPoolbilderError`] - If the thread pool could not be created. /// - /// [`RepositoryErrorKind::FromSplitError`]: crate::error::RepositoryErrorKind::FromSplitError - /// [`RepositoryErrorKind::FromThreadPoolbilderError`]: crate::error::RepositoryErrorKind::FromThreadPoolbilderError + /// [`RusticErrorKind::FromSplitError`]: crate::error::RusticErrorKind::FromSplitError + /// [`RusticErrorKind::FromThreadPoolbilderError`]: crate::error::RusticErrorKind::FromThreadPoolbilderError pub fn warm_up_wait(&self, packs: impl ExactSizeIterator) -> RusticResult<()> { - warm_up_wait(self, packs) + warm_up_wait(self, packs).map_err(|_err| todo!("Error transition")) } } @@ -1116,10 +1099,9 @@ impl Repository { /// If the files could not be deleted. pub fn delete_snapshots(&self, ids: &[SnapshotId]) -> RusticResult<()> { if self.config().append_only == Some(true) { - return Err(CommandErrorKind::NotAllowedWithAppendOnly( - "snapshots removal".to_string(), - ) - .into()); + return Err( + ErrorKind::NotAllowedWithAppendOnly("snapshots removal".to_string()).into(), + ); } let p = self.pb.progress_counter("removing snapshots..."); self.dbe().delete_list(true, ids.iter(), p)?; @@ -1155,6 +1137,9 @@ impl Repository { /// # Errors /// // TODO: Document errors + /// # Panics + /// + /// If the error handling thread panicked pub fn check(&self, opts: CheckOptions) -> RusticResult<()> { let trees = self .get_all_snapshots()? @@ -1162,7 +1147,13 @@ impl Repository { .map(|snap| snap.tree) .collect(); - check_repository(self, opts, trees) + let errors = check_repository(self, opts, trees, err_send)?; + + if errors { + Err(CommandErrorKind::CheckFailed.into()).map_err(|_err| todo!("Error transition")) + } else { + Ok(()) + } } /// Check the repository and given trees for errors or inconsistencies @@ -1174,8 +1165,31 @@ impl Repository { /// # Errors /// // TODO: Document errors + /// # Panics + /// + /// If the error handling thread panicked pub fn check_with_trees(&self, opts: CheckOptions, trees: Vec) -> RusticResult<()> { - check_repository(self, opts, trees) + let (err_send, err_recv) = crossbeam_channel::unbounded(); + + let errors_occurred = Arc::new(AtomicBool::new(false)); + let errors_occurred_clone = errors_occurred.clone(); + + let err_handle = std::thread::spawn(move || { + for err in err_recv { + errors_occurred_clone.store(true, AtomicOrdering::Relaxed); + error!("{}", err); + } + }); + + check_repository(self, opts, trees, err_send)?; + + err_handle.join().expect("Error handling thread panicked"); + + if errors_occurred.load(AtomicOrdering::Relaxed) { + Err(CommandErrorKind::CheckFailed.into()).map_err(|_err| todo!("Error transition")) + } else { + Ok(()) + } } /// Get the plan about what should be pruned and/or repacked. @@ -1582,15 +1596,16 @@ impl Repository { /// /// # Errors /// - /// * [`RepositoryErrorKind::IdNotFound`] - If the id is not found in the index + /// * [`RusticErrorKind::IdNotFound`] - If the id is not found in the index /// - /// [`RepositoryErrorKind::IdNotFound`]: crate::error::RepositoryErrorKind::IdNotFound + /// [`RusticErrorKind::IdNotFound`]: crate::error::RusticErrorKind::IdNotFound pub fn get_index_entry(&self, id: &T) -> RusticResult { let blob_id: BlobId = (*id).into(); let ie = self .index() .get_id(T::TYPE, &blob_id) - .ok_or_else(|| RepositoryErrorKind::IdNotFound(blob_id))?; + .ok_or_else(|| ErrorKind::IdNotFound(blob_id)) + .map_err(|_err| todo!("Error transition"))?; Ok(ie) } diff --git a/crates/core/src/repository/command_input.rs b/crates/core/src/repository/command_input.rs index be9e73b0..2d98e548 100644 --- a/crates/core/src/repository/command_input.rs +++ b/crates/core/src/repository/command_input.rs @@ -109,7 +109,7 @@ impl CommandInput { /// /// # Errors /// - /// `RusticError` if return status cannot be read + /// `CommandInputErrorKind` if return status cannot be read pub fn run(&self, context: &str, what: &str) -> RusticResult<()> { if !self.is_set() { trace!("not calling command {context}:{what} - not set"); @@ -123,7 +123,7 @@ impl CommandInput { } impl FromStr for CommandInput { - type Err = RusticError; + type Err = CommandInputErrorKind; fn from_str(s: &str) -> Result { Ok(Self(_CommandInput::from_str(s)?)) } @@ -165,7 +165,7 @@ impl From> for _CommandInput { } impl FromStr for _CommandInput { - type Err = RusticError; + type Err = CommandInputErrorKind; fn from_str(s: &str) -> Result { Ok(split(s)?.into()) } @@ -192,7 +192,7 @@ pub enum OnFailure { } impl OnFailure { - fn eval(self, res: RusticResult) -> RusticResult> { + fn eval(self, res: CommandInputResult) -> RusticResult> { let res = self.display_result(res); match (res, self) { (Err(err), Self::Error) => Err(err), @@ -202,11 +202,12 @@ impl OnFailure { } /// Displays a result depending on the defined error handling which still yielding the same result + /// /// # Note /// /// This can be used where an error might occur, but in that /// case we have to abort. - pub fn display_result(self, res: RusticResult) -> RusticResult { + pub fn display_result(self, res: CommandInputResult) -> RusticResult { if let Err(err) = &res { match self { Self::Error => { @@ -218,7 +219,7 @@ impl OnFailure { Self::Ignore => {} } } - res + res.map_err(|_err| todo!("Error transition")) } /// Handle a status of a called command depending on the defined error handling @@ -228,20 +229,22 @@ impl OnFailure { context: &str, what: &str, ) -> RusticResult<()> { - let status = status.map_err(|err| { - RepositoryErrorKind::CommandExecutionFailed(context.into(), what.into(), err).into() + let status = status.map_err(|err| CommandInputErrorKind::CommandExecutionFailed { + context: context.to_string(), + what: what.to_string(), + source: err, }); + let Some(status) = self.eval(status)? else { return Ok(()); }; if !status.success() { - let _: Option<()> = self.eval(Err(RepositoryErrorKind::CommandErrorStatus( - context.into(), - what.into(), + let _: Option<()> = self.eval(Err(CommandInputErrorKind::CommandErrorStatus { + context: context.to_string(), + what: what.to_string(), status, - ) - .into()))?; + }))?; } Ok(()) } @@ -249,6 +252,11 @@ impl OnFailure { /// helper to split arguments // TODO: Maybe use special parser (winsplit?) for windows? -fn split(s: &str) -> RusticResult> { - Ok(shell_words::split(s).map_err(|err| RusticErrorKind::Command(err.into()))?) +fn split(s: &str) -> CommandInputResult> { + Ok( + shell_words::split(s).map_err(|err| CommandInputErrorKind::SplittingArgumentsFailed { + arguments: s.to_string(), + source: err, + })?, + ) } diff --git a/crates/core/src/repository/warm_up.rs b/crates/core/src/repository/warm_up.rs index c3805baf..211de677 100644 --- a/crates/core/src/repository/warm_up.rs +++ b/crates/core/src/repository/warm_up.rs @@ -6,7 +6,6 @@ use rayon::ThreadPoolBuilder; use crate::{ backend::{FileType, ReadBackend}, - error::{RepositoryErrorKind, RusticResult}, progress::{Progress, ProgressBars}, repofile::packfile::PackId, repository::Repository, @@ -45,7 +44,7 @@ pub(super) mod constants { pub(crate) fn warm_up_wait( repo: &Repository, packs: impl ExactSizeIterator, -) -> RusticResult<()> { +) -> WarmupResult<()> { warm_up(repo, packs)?; if let Some(wait) = repo.opts.warm_up_wait { let p = repo.pb.progress_spinner(format!("waiting {wait}...")); @@ -72,7 +71,7 @@ pub(crate) fn warm_up_wait( pub(crate) fn warm_up( repo: &Repository, packs: impl ExactSizeIterator, -) -> RusticResult<()> { +) -> WarmupResult<()> { if let Some(warm_up_cmd) = &repo.opts.warm_up_command { warm_up_command(packs, warm_up_cmd, &repo.pb)?; } else if repo.be.needs_warm_up() { @@ -98,7 +97,7 @@ fn warm_up_command( packs: impl ExactSizeIterator, command: &CommandInput, pb: &P, -) -> RusticResult<()> { +) -> WarmupResult<()> { let p = pb.progress_counter("warming up packs..."); p.set_length(packs.len() as u64); for pack in packs { @@ -108,7 +107,10 @@ fn warm_up_command( .map(|c| c.replace("%id", &pack.to_hex())) .collect(); debug!("calling {command:?}..."); - let status = Command::new(command.command()).args(&args).status()?; + let status = Command::new(command.command()) + .args(&args) + .status() + .map_err(|_err| todo!("Error transition"))?; if !status.success() { warn!("warm-up command was not successful for pack {pack:?}. {status}"); } @@ -132,14 +134,14 @@ fn warm_up_command( fn warm_up_repo( repo: &Repository, packs: impl ExactSizeIterator, -) -> RusticResult<()> { +) -> WarmupResult<()> { let progress_bar = repo.pb.progress_counter("warming up packs..."); progress_bar.set_length(packs.len() as u64); let pool = ThreadPoolBuilder::new() .num_threads(constants::MAX_READER_THREADS_NUM) .build() - .map_err(RepositoryErrorKind::FromThreadPoolbilderError)?; + .map_err(|_err| todo!("Error transition"))?; let progress_bar_ref = &progress_bar; let backend = &repo.be; pool.in_place_scope(|scope| { diff --git a/crates/core/src/vfs.rs b/crates/core/src/vfs.rs index 75130556..0ac59256 100644 --- a/crates/core/src/vfs.rs +++ b/crates/core/src/vfs.rs @@ -18,14 +18,11 @@ pub use crate::vfs::webdavfs::WebDavFS; use crate::{ blob::{tree::TreeId, BlobId, DataId}, - error::VfsErrorKind, - repofile::{BlobType, Metadata, Node, NodeType, SnapshotFile}, -}; -use crate::{ + error::RusticResult, index::ReadIndex, + repofile::{BlobType, Metadata, Node, NodeType, SnapshotFile}, repository::{IndexedFull, IndexedTree, Repository}, vfs::format::FormattedSnapshot, - RusticResult, }; /// [`VfsErrorKind`] describes the errors that can be returned from the Virtual File System @@ -109,7 +106,7 @@ impl VfsTree { /// /// [`VfsErrorKind::DirectoryExistsAsNonVirtual`]: crate::error::VfsErrorKind::DirectoryExistsAsNonVirtual /// [`VfsErrorKind::OnlyNormalPathsAreAllowed`]: crate::error::VfsErrorKind::OnlyNormalPathsAreAllowed - fn add_tree(&mut self, path: &Path, new_tree: Self) -> RusticResult<()> { + fn add_tree(&mut self, path: &Path, new_tree: Self) -> VfsResult<()> { let mut tree = self; let mut components = path.components(); let Some(Component::Normal(last)) = components.next_back() else { @@ -152,7 +149,7 @@ impl VfsTree { /// # Returns /// /// If the path is within a real repository tree, this returns the [`VfsTree::RusticTree`] and the remaining path - fn get_path(&self, path: &Path) -> RusticResult> { + fn get_path(&self, path: &Path) -> VfsResult> { let mut tree = self; let mut components = path.components(); loop { @@ -264,7 +261,7 @@ impl Vfs { let filename = path.file_name().map(OsStr::to_os_string); let parent_path = path.parent().map(Path::to_path_buf); - // Save pathes for latest entries, if requested + // Save paths for latest entries, if requested if matches!(latest_option, Latest::AsLink) { _ = dirs_for_link.insert(parent_path.clone(), filename.clone()); } @@ -279,10 +276,12 @@ impl Vfs { && last_tree == snap.tree { if let Some(name) = last_name { - tree.add_tree(path, VfsTree::Link(name))?; + tree.add_tree(path, VfsTree::Link(name)) + .map_err(|_err| todo!("Error transition"))?; } } else { - tree.add_tree(path, VfsTree::RusticTree(snap.tree))?; + tree.add_tree(path, VfsTree::RusticTree(snap.tree)) + .map_err(|_err| todo!("Error transition"))?; } } last_parent = parent_path; @@ -297,7 +296,8 @@ impl Vfs { for (path, target) in dirs_for_link { if let (Some(mut path), Some(target)) = (path, target) { path.push("latest"); - tree.add_tree(&path, VfsTree::Link(target))?; + tree.add_tree(&path, VfsTree::Link(target)) + .map_err(|_err| todo!("Error transition"))?; } } } @@ -305,7 +305,8 @@ impl Vfs { for (path, subtree) in dirs_for_snap { if let Some(mut path) = path { path.push("latest"); - tree.add_tree(&path, VfsTree::RusticTree(subtree))?; + tree.add_tree(&path, VfsTree::RusticTree(subtree)) + .map_err(|_err| todo!("Error transition"))?; } } } @@ -336,7 +337,11 @@ impl Vfs { path: &Path, ) -> RusticResult { let meta = Metadata::default(); - match self.tree.get_path(path)? { + match self + .tree + .get_path(path) + .map_err(|_err| todo!("Error transition"))? + { VfsPath::RusticPath(tree_id, path) => Ok(repo.node_from_path(*tree_id, &path)?), VfsPath::VirtualTree(_) => { Ok(Node::new(String::new(), NodeType::Dir, meta, None, None)) @@ -379,7 +384,11 @@ impl Vfs { repo: &Repository, path: &Path, ) -> RusticResult> { - let result = match self.tree.get_path(path)? { + let result = match self + .tree + .get_path(path) + .map_err(|_err| todo!("Error transition"))? + { VfsPath::RusticPath(tree_id, path) => { let node = repo.node_from_path(*tree_id, &path)?; if node.is_dir() { @@ -400,7 +409,8 @@ impl Vfs { }) .collect(), VfsPath::Link(str) => { - return Err(VfsErrorKind::NoDirectoryEntriesForSymlinkFound(str.clone()).into()); + return Err(VfsErrorKind::NoDirectoryEntriesForSymlinkFound(str.clone())) + .map_err(|_err| todo!("Error transition")); } }; Ok(result) @@ -476,7 +486,7 @@ impl OpenFile { }) .collect(); - // content is assumed to be partioned, so we add a starts_at:MAX entry + // content is assumed to be partitioned, so we add a starts_at:MAX entry content.push(BlobInfo { id: DataId::default(), starts_at: usize::MAX, @@ -511,21 +521,30 @@ impl OpenFile { // find the start of relevant blobs => find the largest index such that self.content[i].starts_at <= offset, but // self.content[i+1] > offset (note that a last dummy element has been added) let mut i = self.content.partition_point(|c| c.starts_at <= offset) - 1; + offset -= self.content[i].starts_at; + let mut result = BytesMut::with_capacity(length); while length > 0 && i < self.content.len() - 1 { let data = repo.get_blob_cached(&BlobId::from(*self.content[i].id), BlobType::Data)?; + if offset > data.len() { // we cannot read behind the blob. This only happens if offset is too large to fit in the last blob break; } + let to_copy = (data.len() - offset).min(length); + result.extend_from_slice(&data[offset..offset + to_copy]); + offset = 0; + length -= to_copy; + i += 1; } + Ok(result.into()) } } diff --git a/crates/core/tests/integration.rs b/crates/core/tests/integration.rs index 4a210234..57d5a63e 100644 --- a/crates/core/tests/integration.rs +++ b/crates/core/tests/integration.rs @@ -253,7 +253,7 @@ fn test_backup_with_tar_gz_passes( // re-read index let repo = repo.to_indexed_ids()?; - // third backup with tags and explicitely given parent + // third backup with tags and explicitly given parent let snap = SnapshotOptions::default() .tags([StringList::from_str("a,b")?]) .to_snapshot()?; From d8421db8206f5d9761a113342c37013f86b5c3fb Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Wed, 23 Oct 2024 18:44:51 +0200 Subject: [PATCH 004/129] fix typo --- crates/core/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/core/CHANGELOG.md b/crates/core/CHANGELOG.md index 642f9d48..7c688dd8 100644 --- a/crates/core/CHANGELOG.md +++ b/crates/core/CHANGELOG.md @@ -109,7 +109,7 @@ All notable changes to this project will be documented in this file. - clippy lints ([#220](https://github.com/rustic-rs/rustic_core/pull/220)) - *(errors)* Show filenames in error message coming from ignore source ([#215](https://github.com/rustic-rs/rustic_core/pull/215)) - *(paths)* Handle paths starting with "." correctly ([#213](https://github.com/rustic-rs/rustic_core/pull/213)) -- Add warning about unsorted files and sort where neccessary ([#205](https://github.com/rustic-rs/rustic_core/pull/205)) +- Add warning about unsorted files and sort where necessary ([#205](https://github.com/rustic-rs/rustic_core/pull/205)) - *(deps)* update rust crate thiserror to 1.0.58 ([#192](https://github.com/rustic-rs/rustic_core/pull/192)) - *(deps)* update rust crate anyhow to 1.0.81 ([#191](https://github.com/rustic-rs/rustic_core/pull/191)) - *(deps)* update rust crate serde_with to 3.7.0 ([#189](https://github.com/rustic-rs/rustic_core/pull/189)) From b690a80874f3f44b99ab06dfd5f529815146a477 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Wed, 23 Oct 2024 18:44:51 +0200 Subject: [PATCH 005/129] style: format changelog --- crates/core/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/core/CHANGELOG.md b/crates/core/CHANGELOG.md index 7c688dd8..2fc5ee5b 100644 --- a/crates/core/CHANGELOG.md +++ b/crates/core/CHANGELOG.md @@ -73,9 +73,11 @@ All notable changes to this project will be documented in this file. ## [0.3.1](https://github.com/rustic-rs/rustic_core/compare/rustic_core-v0.3.0...rustic_core-v0.3.1) - 2024-09-06 ### Added + - Add autocompletion hints ([#257](https://github.com/rustic-rs/rustic_core/pull/257)) ### Fixed + - don't give invalid password error for other keyfile errors ([#247](https://github.com/rustic-rs/rustic_core/pull/247)) - adjust tests to new Rust version ([#259](https://github.com/rustic-rs/rustic_core/pull/259)) - fix FromStr for SnapshotGroupCriterion ([#254](https://github.com/rustic-rs/rustic_core/pull/254)) @@ -86,6 +88,7 @@ All notable changes to this project will be documented in this file. ## [0.3.0](https://github.com/rustic-rs/rustic_core/compare/rustic_core-v0.2.0...rustic_core-v0.3.0) - 2024-08-18 ### Added + - *(forget)* [**breaking**] Make keep-* Options and add keep-none ([#238](https://github.com/rustic-rs/rustic_core/pull/238)) - add search methods to Repository ([#212](https://github.com/rustic-rs/rustic_core/pull/212)) - [**breaking**] Allow specifying many options in config profile without array ([#211](https://github.com/rustic-rs/rustic_core/pull/211)) @@ -98,6 +101,7 @@ All notable changes to this project will be documented in this file. - Add append-only repository mode ([#164](https://github.com/rustic-rs/rustic_core/pull/164)) ### Fixed + - parse commands given by arg or env using shell_words ([#240](https://github.com/rustic-rs/rustic_core/pull/240)) - Allow non-value/null xattr ([#235](https://github.com/rustic-rs/rustic_core/pull/235)) - ensure Rust 1.76.0 compiles @@ -123,6 +127,7 @@ All notable changes to this project will be documented in this file. - updated msrv and fix clippy lints ([#160](https://github.com/rustic-rs/rustic_core/pull/160)) ### Other + - dependency updates - Ensure that MSRV 1.76 works - *(deps)* more version updates ([#237](https://github.com/rustic-rs/rustic_core/pull/237)) From b5a365e52b122aa4be462726f2e79ccd9e111514 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 24 Oct 2024 01:55:09 +0200 Subject: [PATCH 006/129] RusticErrors for Choose, Local and Utils Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/choose.rs | 20 +++++++---- crates/backend/src/local.rs | 67 ++++++++++++++++++++++++------------ crates/backend/src/util.rs | 10 +++--- 3 files changed, 65 insertions(+), 32 deletions(-) diff --git a/crates/backend/src/choose.rs b/crates/backend/src/choose.rs index 3fed2b2c..0419c53f 100644 --- a/crates/backend/src/choose.rs +++ b/crates/backend/src/choose.rs @@ -1,5 +1,6 @@ //! This module contains [`BackendOptions`] and helpers to choose a backend from a given url. use derive_setters::Setters; +use rustic_core::RusticError; use std::{collections::HashMap, sync::Arc}; use strum_macros::{Display, EnumString}; @@ -85,7 +86,12 @@ impl BackendOptions { options.extend(self.options_cold.clone()); let be = self .get_backend(self.repository.as_ref(), options)? - .ok_or_else(|| Err("No repository given.".to_string()).into())?; + .ok_or_else(|| { + RusticError::new( + rustic_core::ErrorKind::Backend, + "No repository given. Please make sure, that you have set the repository.", + ) + })?; let mut options = self.options.clone(); options.extend(self.options_hot.clone()); let be_hot = self.get_backend(self.repo_hot.as_ref(), options)?; @@ -113,15 +119,17 @@ impl BackendOptions { &self, repo_string: Option<&String>, options: HashMap, - ) -> BackendResult>> { + ) -> RusticResult>> { repo_string .map(|string| { let (be_type, location) = location_to_type_and_path(string)?; be_type.to_backend(location, options.into()).map_err(|err| { - BackendErrorKind::BackendLoadError { - name: be_type, - source: err, - } + RusticError::new( + ErrorKind::Backend, + "Could not load the backend. Please check the given backend and try again.", + ) + .add_context("name", be_type) + .source(err.into()) }) }) .transpose() diff --git a/crates/backend/src/local.rs b/crates/backend/src/local.rs index 83994881..9f8babe9 100644 --- a/crates/backend/src/local.rs +++ b/crates/backend/src/local.rs @@ -11,7 +11,8 @@ use log::{debug, trace, warn}; use walkdir::WalkDir; use rustic_core::{ - CommandInput, FileType, Id, ReadBackend, RusticResult, WriteBackend, ALL_FILE_TYPES, + CommandInput, ErrorKind, FileType, Id, ReadBackend, RusticError, RusticResult, WriteBackend, + ALL_FILE_TYPES, }; /// [`LocalBackendErrorKind`] describes the errors that can be returned by an action on the filesystem in Backends @@ -80,16 +81,19 @@ impl LocalBackend { /// # Arguments /// /// * `path` - The base path of the backend + /// * `options` - Additional options for the backend /// - /// # Errors + /// # Returns /// - /// * [`LocalBackendErrorKind::DirectoryCreationFailed`] - If the directory could not be created. + /// A new [`LocalBackend`] instance /// - /// [`LocalBackendErrorKind::DirectoryCreationFailed`]: LocalBackendErrorKind::DirectoryCreationFailed - pub fn new( - path: impl AsRef, - options: impl IntoIterator, - ) -> RusticResult { + /// # Notes + /// + /// The following options are supported: + /// + /// * `post-create-command` - The command to call after a file was created. + /// * `post-delete-command` - The command to call after a file was deleted. + pub fn new(path: impl AsRef, options: impl IntoIterator) -> Self { let path = path.as_ref().into(); let mut post_create_command = None; let mut post_delete_command = None; @@ -107,11 +111,11 @@ impl LocalBackend { } } - Ok(Self { + Self { path, post_create_command, post_delete_command, - }) + } } /// Path to the given file type and id. @@ -146,10 +150,7 @@ impl LocalBackend { /// /// # Errors /// - /// * [`LocalBackendErrorKind::FromAhoCorasick`] - If the patterns could not be compiled. - /// * [`LocalBackendErrorKind::FromSplitError`] - If the command could not be parsed. - /// * [`LocalBackendErrorKind::CommandExecutionFailed`] - If the command could not be executed. - /// * [`LocalBackendErrorKind::CommandNotSuccessful`] - If the command was not successful. + // TODO: Add error types /// /// # Notes /// @@ -157,23 +158,45 @@ impl LocalBackend { /// * `%file` - The path to the file. /// * `%type` - The type of the file. /// * `%id` - The id of the file. - /// - /// [`LocalBackendErrorKind::FromAhoCorasick`]: LocalBackendErrorKind::FromAhoCorasick - /// [`LocalBackendErrorKind::FromSplitError`]: LocalBackendErrorKind::FromSplitError - /// [`LocalBackendErrorKind::CommandExecutionFailed`]: LocalBackendErrorKind::CommandExecutionFailed - /// [`LocalBackendErrorKind::CommandNotSuccessful`]: LocalBackendErrorKind::CommandNotSuccessful fn call_command(tpe: FileType, id: &Id, filename: &Path, command: &str) -> RusticResult<()> { let id = id.to_hex(); + let patterns = &["%file", "%type", "%id"]; - let ac = AhoCorasick::new(patterns).map_err(LocalBackendErrorKind::FromAhoCorasick)?; + + let ac = AhoCorasick::new(patterns).map_err(|err| { + RusticError::new(ErrorKind::Backend, + "Experienced an error building AhoCorasick automaton for command replacement. This is a bug. Please report it.") + .source(err.into()) + })?; + let replace_with = &[filename.to_str().unwrap(), tpe.dirname(), id.as_str()]; + let actual_command = ac.replace_all(command, replace_with); + debug!("calling {actual_command}..."); - let command: CommandInput = actual_command.parse()?; + + let command: CommandInput = actual_command.parse().map_err(|err| { + RusticError::new( + ErrorKind::Backend, + "Failed to parse command input. This is a bug. Please report it.", + ) + .add_context("command", actual_command) + .add_context("replacement", replace_with.join(", ")) + .source(err.into()) + })?; + let status = Command::new(command.command()) .args(command.args()) .status() - .map_err(LocalBackendErrorKind::CommandExecutionFailed)?; + .map_err(|err| { + RusticError::new( + ErrorKind::Backend, + "Failed to execute command. Please check the command and try again.", + ) + .add_context("command", command) + .source(err.into()) + })?; + if !status.success() { return Err(LocalBackendErrorKind::CommandNotSuccessful { file_name: replace_with[0].to_owned(), diff --git a/crates/backend/src/util.rs b/crates/backend/src/util.rs index 642b722d..78a98a4b 100644 --- a/crates/backend/src/util.rs +++ b/crates/backend/src/util.rs @@ -1,5 +1,5 @@ use crate::SupportedBackend; -use rustic_core::{BackendErrorKind, BackendResult}; +use rustic_core::{BackendErrorKind, BackendResult, RusticError}; /// A backend location. This is a string that represents the location of the backend. #[derive(PartialEq, Eq, Debug)] @@ -65,9 +65,11 @@ pub fn location_to_type_and_path( SupportedBackend::Local, BackendLocation(raw_location.to_string()), )), - _ => Err(BackendErrorKind::BackendLocationNotConvertible { - location: raw_location.to_string(), - }), + _ => Err(RusticError::new( + ErrorKind::Backend, + "The location is not convertible to a backend location. Please check the given location and try again.", + ) + .add_context("location", raw_location)), } } From 36c0ca1385d7d97a3e681254ff13772a4347ab62 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 24 Oct 2024 02:11:03 +0200 Subject: [PATCH 007/129] RusticErrors for Local Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/local.rs | 184 ++++++++++++++++++++++++++++-------- 1 file changed, 142 insertions(+), 42 deletions(-) diff --git a/crates/backend/src/local.rs b/crates/backend/src/local.rs index 9f8babe9..d7552547 100644 --- a/crates/backend/src/local.rs +++ b/crates/backend/src/local.rs @@ -271,10 +271,24 @@ impl ReadBackend for LocalBackend { vec![( Id::default(), path.metadata() - .map_err(LocalBackendErrorKind::QueryingMetadataFailed)? + .map_err(|err| + RusticError::new( + ErrorKind::Backend, + "Failed to query metadata of the file. Please check the file and try again.", + ) + .add_context("path", path.to_string_lossy()) + .source(err.into()) + )? .len() .try_into() - .map_err(LocalBackendErrorKind::FromTryIntError)?, + .map_err(|err| + RusticError::new( + ErrorKind::Backend, + "Failed to convert file length to u32. This is a bug. Please report it.", + ) + .add_context("length", path.metadata().unwrap().len()) + .source(err.into()) + )?, )] } else { Vec::new() @@ -289,12 +303,25 @@ impl ReadBackend for LocalBackend { Ok(( e.file_name().to_string_lossy().parse()?, e.metadata() - .map_err(LocalBackendErrorKind::QueryingWalkDirMetadataFailed) - .map_err(|_err| todo!("Error transition"))? + .map_err(|err| + RusticError::new( + ErrorKind::Backend, + "Failed to query metadata of the file. Please check the file and try again.", + ) + .add_context("path", e.path().to_string_lossy()) + .source(err.into()) + ) + ? .len() .try_into() - .map_err(LocalBackendErrorKind::FromTryIntError) - .map_err(|_err| todo!("Error transition"))?, + .map_err(|err| + RusticError::new( + ErrorKind::Backend, + "Failed to convert file length to u32. This is a bug. Please report it.", + ) + .add_context("length", e.metadata().unwrap().len()) + .source(err.into()) + )?, )) }) .filter_map(RusticResult::ok); @@ -311,13 +338,19 @@ impl ReadBackend for LocalBackend { /// /// # Errors /// - /// * [`LocalBackendErrorKind::ReadingContentsOfFileFailed`] - If the file could not be read. - /// - /// [`LocalBackendErrorKind::ReadingContentsOfFileFailed`]: LocalBackendErrorKind::ReadingContentsOfFileFailed + /// - If the file could not be read. + /// - If the file could not be found. fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult { trace!("reading tpe: {tpe:?}, id: {id}"); Ok(fs::read(self.path(tpe, id)) - .map_err(LocalBackendErrorKind::ReadingContentsOfFileFailed)? + .map_err(|err| + RusticError::new( + ErrorKind::Backend, + "Failed to read the contents of the file. Please check the file and try again.", + ) + .add_context("path", self.path(tpe, id).to_string_lossy()) + .source(err.into()) + )? .into()) } @@ -333,15 +366,7 @@ impl ReadBackend for LocalBackend { /// /// # Errors /// - /// * [`LocalBackendErrorKind::OpeningFileFailed`] - If the file could not be opened. - /// * [`LocalBackendErrorKind::CouldNotSeekToPositionInFile`] - If the file could not be sought to the given position. - /// * [`LocalBackendErrorKind::FromTryIntError`] - If the length of the file could not be converted to u32. - /// * [`LocalBackendErrorKind::ReadingExactLengthOfFileFailed`] - If the length of the file could not be read. - /// - /// [`LocalBackendErrorKind::OpeningFileFailed`]: LocalBackendErrorKind::OpeningFileFailed - /// [`LocalBackendErrorKind::CouldNotSeekToPositionInFile`]: LocalBackendErrorKind::CouldNotSeekToPositionInFile - /// [`LocalBackendErrorKind::FromTryIntError`]: LocalBackendErrorKind::FromTryIntError - /// [`LocalBackendErrorKind::ReadingExactLengthOfFileFailed`]: LocalBackendErrorKind::ReadingExactLengthOfFileFailed + /// fn read_partial( &self, tpe: FileType, @@ -352,18 +377,48 @@ impl ReadBackend for LocalBackend { ) -> RusticResult { trace!("reading tpe: {tpe:?}, id: {id}, offset: {offset}, length: {length}"); let mut file = - File::open(self.path(tpe, id)).map_err(LocalBackendErrorKind::OpeningFileFailed)?; + File::open(self.path(tpe, id)).map_err(|err| + RusticError::new( + ErrorKind::Backend, + "Failed to open the file. Please check the file and try again.", + ) + .add_context("path", self.path(tpe, id).to_string_lossy()) + .source(err.into()) + )?; _ = file .seek(SeekFrom::Start(offset.into())) - .map_err(LocalBackendErrorKind::CouldNotSeekToPositionInFile)?; + .map_err(|err| + RusticError::new( + ErrorKind::Backend, + "Failed to seek to the position in the file. Please check the file and try again.", + ) + .add_context("path", self.path(tpe, id).to_string_lossy()) + .add_context("offset", offset) + .source(err.into()) + )?; let mut vec = vec![ 0; length .try_into() - .map_err(LocalBackendErrorKind::FromTryIntError)? + .map_err(|err| + RusticError::new( + ErrorKind::Backend, + "Failed to convert length to u64. This is a bug. Please report it.", + ) + .add_context("length", length) + .source(err.into()) + )? ]; file.read_exact(&mut vec) - .map_err(LocalBackendErrorKind::ReadingExactLengthOfFileFailed)?; + .map_err(|err| + RusticError::new( + ErrorKind::Backend, + "Failed to read the exact length of the file. Please check the file and try again.", + ) + .add_context("path", self.path(tpe, id).to_string_lossy()) + .add_context("length", length) + .source(err.into()) + )?; Ok(vec.into()) } } @@ -378,15 +433,36 @@ impl WriteBackend for LocalBackend { /// [`LocalBackendErrorKind::DirectoryCreationFailed`]: LocalBackendErrorKind::DirectoryCreationFailed fn create(&self) -> RusticResult<()> { trace!("creating repo at {:?}", self.path); - fs::create_dir_all(&self.path).map_err(LocalBackendErrorKind::DirectoryCreationFailed)?; + fs::create_dir_all(&self.path).map_err(|err| + RusticError::new( + ErrorKind::Backend, + "Failed to create the directory. Please check the path and try again.", + ) + .add_context("path", self.path.to_string_lossy()) + .source(err.into()) + )?; for tpe in ALL_FILE_TYPES { fs::create_dir_all(self.path.join(tpe.dirname())) - .map_err(LocalBackendErrorKind::DirectoryCreationFailed)?; + .map_err(|err| + RusticError::new( + ErrorKind::Backend, + "Failed to create the directory. Please check the path and try again.", + ) + .add_context("path", self.path.join(tpe.dirname()).to_string_lossy()) + .source(err.into()) + )?; } for i in 0u8..=255 { fs::create_dir_all(self.path.join("data").join(hex::encode([i]))) - .map_err(LocalBackendErrorKind::DirectoryCreationFailed)?; + .map_err(|err| + RusticError::new( + ErrorKind::Backend, + "Failed to create the directory. Please check the path and try again.", + ) + .add_context("path", self.path.join("data").join(hex::encode([i])).to_string_lossy()) + .source(err.into()) + )?; } Ok(()) } @@ -402,17 +478,6 @@ impl WriteBackend for LocalBackend { /// /// # Errors /// - /// * [`LocalBackendErrorKind::OpeningFileFailed`] - If the file could not be opened. - /// * [`LocalBackendErrorKind::FromTryIntError`] - If the length of the bytes could not be converted to u64. - /// * [`LocalBackendErrorKind::SettingFileLengthFailed`] - If the length of the file could not be set. - /// * [`LocalBackendErrorKind::CouldNotWriteToBuffer`] - If the bytes could not be written to the file. - /// * [`LocalBackendErrorKind::SyncingOfOsMetadataFailed`] - If the metadata of the file could not be synced. - /// - /// [`LocalBackendErrorKind::OpeningFileFailed`]: LocalBackendErrorKind::OpeningFileFailed - /// [`LocalBackendErrorKind::FromTryIntError`]: LocalBackendErrorKind::FromTryIntError - /// [`LocalBackendErrorKind::SettingFileLengthFailed`]: LocalBackendErrorKind::SettingFileLengthFailed - /// [`LocalBackendErrorKind::CouldNotWriteToBuffer`]: LocalBackendErrorKind::CouldNotWriteToBuffer - /// [`LocalBackendErrorKind::SyncingOfOsMetadataFailed`]: LocalBackendErrorKind::SyncingOfOsMetadataFailed fn write_bytes( &self, tpe: FileType, @@ -427,17 +492,52 @@ impl WriteBackend for LocalBackend { .truncate(true) .write(true) .open(&filename) - .map_err(LocalBackendErrorKind::OpeningFileFailed)?; + .map_err(|err| + RusticError::new( + ErrorKind::Backend, + "Failed to open the file. Please check the file and try again.", + ) + .add_context("path", filename.to_string_lossy()) + .source(err.into()) + )?; file.set_len( buf.len() .try_into() - .map_err(LocalBackendErrorKind::FromTryIntError)?, + .map_err(|err| + RusticError::new( + ErrorKind::Backend, + "Failed to convert length to u64. This is a bug. Please report it.", + ) + .add_context("length", buf.len()) + .source(err.into()) + )?, ) - .map_err(LocalBackendErrorKind::SettingFileLengthFailed)?; + .map_err(|err| + RusticError::new( + ErrorKind::Backend, + "Failed to set the length of the file. Please check the file and try again.", + ) + .add_context("path", filename.to_string_lossy()) + .source(err.into()) + )?; file.write_all(&buf) - .map_err(LocalBackendErrorKind::CouldNotWriteToBuffer)?; + .map_err(|err| + RusticError::new( + ErrorKind::Backend, + "Failed to write to the buffer. Please check the file and try again.", + ) + .add_context("path", filename.to_string_lossy()) + .source(err.into()) + )?; file.sync_all() - .map_err(LocalBackendErrorKind::SyncingOfOsMetadataFailed)?; + .map_err(|err| + RusticError::new( + ErrorKind::Backend, + "Failed to sync OS Metadata to disk. Please check the file and try again.", + ) + .add_context("path", filename.to_string_lossy()) + .source(err.into()) + )?; if let Some(command) = &self.post_create_command { if let Err(err) = Self::call_command(tpe, id, &filename, command) { warn!("post-create: {err}"); From 93f99b22f3e2051dc17e04c23266cf36b284ea20 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 24 Oct 2024 02:12:59 +0200 Subject: [PATCH 008/129] RusticErrors for Local Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/local.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/backend/src/local.rs b/crates/backend/src/local.rs index d7552547..bb2baf72 100644 --- a/crates/backend/src/local.rs +++ b/crates/backend/src/local.rs @@ -562,7 +562,14 @@ impl WriteBackend for LocalBackend { fn remove(&self, tpe: FileType, id: &Id, _cacheable: bool) -> RusticResult<()> { trace!("removing tpe: {:?}, id: {}", &tpe, &id); let filename = self.path(tpe, id); - fs::remove_file(&filename).map_err(LocalBackendErrorKind::FileRemovalFailed)?; + fs::remove_file(&filename).map_err(|err| + RusticError::new( + ErrorKind::Backend, + "Failed to remove the file. Was the file already removed or is it in use? Please check the file and remove it manually.", + ) + .add_context("path", filename.to_string_lossy()) + .source(err.into()) + )?; if let Some(command) = &self.post_delete_command { if let Err(err) = Self::call_command(tpe, id, &filename, command) { warn!("post-delete: {err}"); From 96b451679eacbc811f78c036b48b724c3ceb102a Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 24 Oct 2024 02:17:17 +0200 Subject: [PATCH 009/129] style: cargo fmt Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/local.rs | 190 ++++++++++++++++------------------ crates/core/src/id.rs | 4 +- crates/core/src/repository.rs | 5 +- 3 files changed, 94 insertions(+), 105 deletions(-) diff --git a/crates/backend/src/local.rs b/crates/backend/src/local.rs index bb2baf72..846449e7 100644 --- a/crates/backend/src/local.rs +++ b/crates/backend/src/local.rs @@ -271,7 +271,7 @@ impl ReadBackend for LocalBackend { vec![( Id::default(), path.metadata() - .map_err(|err| + .map_err(|err| RusticError::new( ErrorKind::Backend, "Failed to query metadata of the file. Please check the file and try again.", @@ -281,7 +281,7 @@ impl ReadBackend for LocalBackend { )? .len() .try_into() - .map_err(|err| + .map_err(|err| RusticError::new( ErrorKind::Backend, "Failed to convert file length to u32. This is a bug. Please report it.", @@ -303,7 +303,7 @@ impl ReadBackend for LocalBackend { Ok(( e.file_name().to_string_lossy().parse()?, e.metadata() - .map_err(|err| + .map_err(|err| RusticError::new( ErrorKind::Backend, "Failed to query metadata of the file. Please check the file and try again.", @@ -343,14 +343,14 @@ impl ReadBackend for LocalBackend { fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult { trace!("reading tpe: {tpe:?}, id: {id}"); Ok(fs::read(self.path(tpe, id)) - .map_err(|err| + .map_err(|err| { RusticError::new( ErrorKind::Backend, "Failed to read the contents of the file. Please check the file and try again.", ) .add_context("path", self.path(tpe, id).to_string_lossy()) .source(err.into()) - )? + })? .into()) } @@ -366,7 +366,7 @@ impl ReadBackend for LocalBackend { /// /// # Errors /// - /// + /// fn read_partial( &self, tpe: FileType, @@ -376,49 +376,41 @@ impl ReadBackend for LocalBackend { length: u32, ) -> RusticResult { trace!("reading tpe: {tpe:?}, id: {id}, offset: {offset}, length: {length}"); - let mut file = - File::open(self.path(tpe, id)).map_err(|err| - RusticError::new( - ErrorKind::Backend, - "Failed to open the file. Please check the file and try again.", - ) - .add_context("path", self.path(tpe, id).to_string_lossy()) - .source(err.into()) - )?; - _ = file - .seek(SeekFrom::Start(offset.into())) - .map_err(|err| - RusticError::new( - ErrorKind::Backend, - "Failed to seek to the position in the file. Please check the file and try again.", - ) - .add_context("path", self.path(tpe, id).to_string_lossy()) - .add_context("offset", offset) - .source(err.into()) - )?; + let mut file = File::open(self.path(tpe, id)).map_err(|err| { + RusticError::new( + ErrorKind::Backend, + "Failed to open the file. Please check the file and try again.", + ) + .add_context("path", self.path(tpe, id).to_string_lossy()) + .source(err.into()) + })?; + _ = file.seek(SeekFrom::Start(offset.into())).map_err(|err| { + RusticError::new( + ErrorKind::Backend, + "Failed to seek to the position in the file. Please check the file and try again.", + ) + .add_context("path", self.path(tpe, id).to_string_lossy()) + .add_context("offset", offset) + .source(err.into()) + })?; let mut vec = vec![ 0; - length - .try_into() - .map_err(|err| - RusticError::new( - ErrorKind::Backend, - "Failed to convert length to u64. This is a bug. Please report it.", - ) - .add_context("length", length) - .source(err.into()) - )? + length.try_into().map_err(|err| RusticError::new( + ErrorKind::Backend, + "Failed to convert length to u64. This is a bug. Please report it.", + ) + .add_context("length", length) + .source(err.into()))? ]; - file.read_exact(&mut vec) - .map_err(|err| - RusticError::new( - ErrorKind::Backend, - "Failed to read the exact length of the file. Please check the file and try again.", - ) - .add_context("path", self.path(tpe, id).to_string_lossy()) - .add_context("length", length) - .source(err.into()) - )?; + file.read_exact(&mut vec).map_err(|err| { + RusticError::new( + ErrorKind::Backend, + "Failed to read the exact length of the file. Please check the file and try again.", + ) + .add_context("path", self.path(tpe, id).to_string_lossy()) + .add_context("length", length) + .source(err.into()) + })?; Ok(vec.into()) } } @@ -433,36 +425,40 @@ impl WriteBackend for LocalBackend { /// [`LocalBackendErrorKind::DirectoryCreationFailed`]: LocalBackendErrorKind::DirectoryCreationFailed fn create(&self) -> RusticResult<()> { trace!("creating repo at {:?}", self.path); - fs::create_dir_all(&self.path).map_err(|err| + fs::create_dir_all(&self.path).map_err(|err| { RusticError::new( ErrorKind::Backend, "Failed to create the directory. Please check the path and try again.", ) .add_context("path", self.path.to_string_lossy()) .source(err.into()) - )?; + })?; for tpe in ALL_FILE_TYPES { - fs::create_dir_all(self.path.join(tpe.dirname())) - .map_err(|err| - RusticError::new( - ErrorKind::Backend, - "Failed to create the directory. Please check the path and try again.", - ) - .add_context("path", self.path.join(tpe.dirname()).to_string_lossy()) - .source(err.into()) - )?; + fs::create_dir_all(self.path.join(tpe.dirname())).map_err(|err| { + RusticError::new( + ErrorKind::Backend, + "Failed to create the directory. Please check the path and try again.", + ) + .add_context("path", self.path.join(tpe.dirname()).to_string_lossy()) + .source(err.into()) + })?; } for i in 0u8..=255 { - fs::create_dir_all(self.path.join("data").join(hex::encode([i]))) - .map_err(|err| - RusticError::new( - ErrorKind::Backend, - "Failed to create the directory. Please check the path and try again.", - ) - .add_context("path", self.path.join("data").join(hex::encode([i])).to_string_lossy()) - .source(err.into()) - )?; + fs::create_dir_all(self.path.join("data").join(hex::encode([i]))).map_err(|err| { + RusticError::new( + ErrorKind::Backend, + "Failed to create the directory. Please check the path and try again.", + ) + .add_context( + "path", + self.path + .join("data") + .join(hex::encode([i])) + .to_string_lossy(), + ) + .source(err.into()) + })?; } Ok(()) } @@ -492,52 +488,46 @@ impl WriteBackend for LocalBackend { .truncate(true) .write(true) .open(&filename) - .map_err(|err| + .map_err(|err| { RusticError::new( ErrorKind::Backend, "Failed to open the file. Please check the file and try again.", ) .add_context("path", filename.to_string_lossy()) .source(err.into()) - )?; - file.set_len( - buf.len() - .try_into() - .map_err(|err| - RusticError::new( - ErrorKind::Backend, - "Failed to convert length to u64. This is a bug. Please report it.", - ) - .add_context("length", buf.len()) - .source(err.into()) - )?, - ) - .map_err(|err| + })?; + file.set_len(buf.len().try_into().map_err(|err| { + RusticError::new( + ErrorKind::Backend, + "Failed to convert length to u64. This is a bug. Please report it.", + ) + .add_context("length", buf.len()) + .source(err.into()) + })?) + .map_err(|err| { RusticError::new( ErrorKind::Backend, "Failed to set the length of the file. Please check the file and try again.", ) .add_context("path", filename.to_string_lossy()) .source(err.into()) - )?; - file.write_all(&buf) - .map_err(|err| - RusticError::new( - ErrorKind::Backend, - "Failed to write to the buffer. Please check the file and try again.", - ) - .add_context("path", filename.to_string_lossy()) - .source(err.into()) - )?; - file.sync_all() - .map_err(|err| - RusticError::new( - ErrorKind::Backend, - "Failed to sync OS Metadata to disk. Please check the file and try again.", - ) - .add_context("path", filename.to_string_lossy()) - .source(err.into()) - )?; + })?; + file.write_all(&buf).map_err(|err| { + RusticError::new( + ErrorKind::Backend, + "Failed to write to the buffer. Please check the file and try again.", + ) + .add_context("path", filename.to_string_lossy()) + .source(err.into()) + })?; + file.sync_all().map_err(|err| { + RusticError::new( + ErrorKind::Backend, + "Failed to sync OS Metadata to disk. Please check the file and try again.", + ) + .add_context("path", filename.to_string_lossy()) + .source(err.into()) + })?; if let Some(command) = &self.post_create_command { if let Err(err) = Self::call_command(tpe, id, &filename, command) { warn!("post-create: {err}"); diff --git a/crates/core/src/id.rs b/crates/core/src/id.rs index 6f1de2eb..2dee6601 100644 --- a/crates/core/src/id.rs +++ b/crates/core/src/id.rs @@ -9,7 +9,7 @@ use serde_derive::{Deserialize, Serialize}; use crate::{ crypto::hasher::hash, - error::{RusticError, ErrorKind, RusticResult}, + error::{ErrorKind, RusticError, RusticResult}, }; pub(super) mod constants { @@ -86,7 +86,7 @@ impl FromStr for Id { fn from_str(s: &str) -> Result { let mut id = Self::default(); hex::decode_to_slice(s, &mut id.0).map_err(|err| { - RusticError::new(ErrorKind::Parsing, + RusticError::new(ErrorKind::Parsing, format!("Failed to decode hex string into Id. The string must be a valid hexadecimal string: {s}") ).source(err.into()) })?; diff --git a/crates/core/src/repository.rs b/crates/core/src/repository.rs index 9d77bd59..5152edf5 100644 --- a/crates/core/src/repository.rs +++ b/crates/core/src/repository.rs @@ -190,9 +190,8 @@ impl RepositoryOptions { match (&self.password, &self.password_file, &self.password_command) { (Some(pwd), _, _) => Ok(Some(pwd.clone())), (_, Some(file), _) => { - let mut file = BufReader::new( - File::open(file).map_err(ErrorKind::OpeningPasswordFileFailed)?, - ); + let mut file = + BufReader::new(File::open(file).map_err(ErrorKind::OpeningPasswordFileFailed)?); Ok(Some(read_password_from_reader(&mut file)?)) } (_, _, Some(command)) if command.is_set() => { From d51a6ca0bcf93f799e3718694ac4111b9c7df4b0 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 24 Oct 2024 02:19:58 +0200 Subject: [PATCH 010/129] remove leftovers from check command error handling draft Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/repository.rs | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/crates/core/src/repository.rs b/crates/core/src/repository.rs index 5152edf5..15a0a196 100644 --- a/crates/core/src/repository.rs +++ b/crates/core/src/repository.rs @@ -1168,27 +1168,7 @@ impl Repository { /// /// If the error handling thread panicked pub fn check_with_trees(&self, opts: CheckOptions, trees: Vec) -> RusticResult<()> { - let (err_send, err_recv) = crossbeam_channel::unbounded(); - - let errors_occurred = Arc::new(AtomicBool::new(false)); - let errors_occurred_clone = errors_occurred.clone(); - - let err_handle = std::thread::spawn(move || { - for err in err_recv { - errors_occurred_clone.store(true, AtomicOrdering::Relaxed); - error!("{}", err); - } - }); - - check_repository(self, opts, trees, err_send)?; - - err_handle.join().expect("Error handling thread panicked"); - - if errors_occurred.load(AtomicOrdering::Relaxed) { - Err(CommandErrorKind::CheckFailed.into()).map_err(|_err| todo!("Error transition")) - } else { - Ok(()) - } + check_repository(self, opts, trees) } /// Get the plan about what should be pruned and/or repacked. From 8af508d0d2b9aea523c4e5d5e48d43f980a97d16 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 24 Oct 2024 02:41:39 +0200 Subject: [PATCH 011/129] RusticErrors for Check Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/commands/check.rs | 90 ++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 31 deletions(-) diff --git a/crates/core/src/commands/check.rs b/crates/core/src/commands/check.rs index 743259a6..524b7226 100644 --- a/crates/core/src/commands/check.rs +++ b/crates/core/src/commands/check.rs @@ -2,6 +2,7 @@ use std::{ collections::{BTreeSet, HashMap}, fmt::Debug, + num::{ParseFloatError, ParseIntError}, path::PathBuf, str::FromStr, }; @@ -30,13 +31,12 @@ use crate::{ packfile::PackId, IndexFile, IndexPack, PackHeader, PackHeaderLength, PackHeaderRef, }, repository::{Open, Repository}, - TreeId, + ErrorKind, TreeId, }; #[non_exhaustive] #[derive(thiserror::Error, Debug, displaydoc::Display)] -#[non_exhaustive] -pub enum CheckCheckCommandErrorKind { +pub enum CheckCommandErrorKind { /// error reading pack {id} : {source} ErrorReadingPack { id: PackId, @@ -125,7 +125,7 @@ pub enum CheckCheckCommandErrorKind { }, } -pub(crate) type CheckResult = Result<(), Vec>; +pub(crate) type CheckCommandResult = Result<(), CheckCommandErrorKind>; #[derive(Clone, Copy, Debug, Default)] #[non_exhaustive] @@ -193,11 +193,7 @@ impl ReadSubsetOption { } /// parses n/m including named settings depending on current date -fn parse_n_m( - now: NaiveDateTime, - n_in: &str, - m_in: &str, -) -> Result<(u32, u32), CheckCommandErrorKind> { +fn parse_n_m(now: NaiveDateTime, n_in: &str, m_in: &str) -> Result<(u32, u32), ParseIntError> { let is_leap_year = |dt: NaiveDateTime| { let year = dt.year(); year % 4 == 0 && (year % 25 != 0 || year % 16 == 0) @@ -238,20 +234,44 @@ fn parse_n_m( } impl FromStr for ReadSubsetOption { - type Err = CheckCommandErrorKind; + type Err = RusticError; fn from_str(s: &str) -> Result { let result = if s == "all" { Self::All } else if let Some(p) = s.strip_suffix('%') { // try to read percentage - Self::Percentage(p.parse()?) + Self::Percentage(p.parse().map_err(|err: ParseFloatError| { + RusticError::new( + ErrorKind::Parsing, + "Error parsing percentage for ReadSubset option. Did you forget the '%'?", + ) + .add_context("value", p.to_string()) + .source(err.into()) + })?) } else if let Some((n, m)) = s.split_once('/') { let now = Local::now().naive_local(); - Self::IdSubSet(parse_n_m(now, n, m)?) + Self::IdSubSet(parse_n_m(now, n, m).map_err( + |err| + RusticError::new( + ErrorKind::Parsing, + "Error parsing n/m for ReadSubset option. Allowed values: 'all', 'x%', 'n/m' or a size.", + ) + .add_context("value", s) + .add_context("n/m", format!("{}/{}", n, m)) + .add_context("now", now.to_string()) + .source(err.into()) + )?) } else { Self::Size( ByteSize::from_str(s) - .map_err(CheckCommandErrorKind::FromByteSizeParser)? + .map_err(|err| { + RusticError::new( + ErrorKind::Parsing, + "Error parsing size for ReadSubset option. Allowed values: 'all', 'x%', 'n/m' or a size.", + ) + .add_context("value", s) + .source(err.into()) + })? .as_u64(), ) }; @@ -318,9 +338,7 @@ pub(crate) fn check_repository( // // This lists files here and later when reading index / checking snapshots // TODO: Only list the files once... - _ = be - .list_with_size(file_type) - .map_err(RusticErrorKind::Backend)?; + _ = be.list_with_size(file_type)?; let p = pb.progress_bytes(format!("checking {file_type:?} in cache...")); // TODO: Make concurrency (20) customizable @@ -408,14 +426,11 @@ fn check_hot_files( ) -> RusticResult<()> { let p = pb.progress_spinner(format!("checking {file_type:?} in hot repo...")); let mut files = be - .list_with_size(file_type) - .map_err(RusticErrorKind::Backend)? + .list_with_size(file_type)? .into_iter() .collect::>(); - let files_hot = be_hot - .list_with_size(file_type) - .map_err(RusticErrorKind::Backend)?; + let files_hot = be_hot.list_with_size(file_type)?; for (id, size_hot) in files_hot { match files.remove(&id) { @@ -587,10 +602,7 @@ fn check_packs( /// /// If a pack is missing or has a different size fn check_packs_list(be: &impl ReadBackend, mut packs: HashMap) -> RusticResult<()> { - for (id, size) in be - .list_with_size(FileType::Pack) - .map_err(RusticErrorKind::Backend)? - { + for (id, size) in be.list_with_size(FileType::Pack)? { match packs.remove(&PackId::from(id)) { None => warn!("pack {id} not referenced in index. Can be a parallel backup job. To repair: 'rustic repair index'."), Some(index_size) if index_size != size => { @@ -621,10 +633,7 @@ fn check_packs_list_hot( mut treepacks: HashMap, packs: &HashMap, ) -> RusticResult<()> { - for (id, size) in be - .list_with_size(FileType::Pack) - .map_err(RusticErrorKind::Backend)? - { + for (id, size) in be.list_with_size(FileType::Pack)? { match treepacks.remove(&PackId::from(id)) { None => { if packs.contains_key(&PackId::from(id)) { @@ -766,7 +775,17 @@ fn check_pack( // check header length let header_len = PackHeaderRef::from_index_pack(&index_pack).size(); - let pack_header_len = PackHeaderLength::from_binary(&data.split_off(data.len() - 4))?.to_u32(); + let pack_header_len = PackHeaderLength::from_binary(&data.split_off(data.len() - 4)) + .map_err(|err| { + RusticError::new( + ErrorKind::Command, + "Error reading pack header length. This is a bug. Please report this error.", + ) + .add_context("pack id", id.to_string()) + .add_context("header length", header_len.to_string()) + .source(err.into()) + })? + .to_u32(); if pack_header_len != header_len { error!("pack {id}: Header length in pack file doesn't match index. In pack: {pack_header_len}, calculated: {header_len}"); return Ok(()); @@ -775,7 +794,16 @@ fn check_pack( // check header let header = be.decrypt(&data.split_off(data.len() - header_len as usize))?; - let pack_blobs = PackHeader::from_binary(&header)?.into_blobs(); + let pack_blobs = PackHeader::from_binary(&header) + .map_err(|err| { + RusticError::new( + ErrorKind::Command, + "Error reading pack header. This is a bug. Please report this error.", + ) + .add_context("pack id", id.to_string()) + .source(err.into()) + })? + .into_blobs(); let mut blobs = index_pack.blobs; blobs.sort_unstable_by_key(|b| b.offset); if pack_blobs != blobs { From 0ecf2889d8ae1c8d489d0dc486fb44ca5330206e Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 24 Oct 2024 02:46:10 +0200 Subject: [PATCH 012/129] RusticErrors for Cat Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/commands/cat.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/core/src/commands/cat.rs b/crates/core/src/commands/cat.rs index 38fa566d..78e1f119 100644 --- a/crates/core/src/commands/cat.rs +++ b/crates/core/src/commands/cat.rs @@ -10,6 +10,7 @@ use crate::{ progress::ProgressBars, repofile::SnapshotFile, repository::{IndexedFull, IndexedTree, Open, Repository}, + ErrorKind, RusticError, }; /// Prints the contents of a file. @@ -112,10 +113,12 @@ pub(crate) fn cat_tree( &repo.pb.progress_counter("getting snapshot..."), )?; let node = Tree::node_from_path(repo.dbe(), repo.index(), snap.tree, Path::new(path))?; - let id = node - .subtree - .ok_or_else(|| CommandErrorKind::PathIsNoDir(path.to_string())) - .map_err(|_err| todo!("Error transition"))?; + let id = node.subtree.ok_or_else(|| { + RusticError::new( + ErrorKind::Command, + "Path in Node subtree is not a directory. Please provide a directory path.", + ) + })?; let data = repo .index() .blob_from_backend(repo.dbe(), BlobType::Tree, &BlobId::from(*id))?; From 17f8a354ddc6ddbb0776d1849e96c0a0553df231 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 24 Oct 2024 02:54:49 +0200 Subject: [PATCH 013/129] add context for cat Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/commands/cat.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/core/src/commands/cat.rs b/crates/core/src/commands/cat.rs index 78e1f119..97f0ffd2 100644 --- a/crates/core/src/commands/cat.rs +++ b/crates/core/src/commands/cat.rs @@ -118,6 +118,7 @@ pub(crate) fn cat_tree( ErrorKind::Command, "Path in Node subtree is not a directory. Please provide a directory path.", ) + .add_context("path", path.to_string()) })?; let data = repo .index() From b507c5b4cf09c1800ab8be5356f3aaa056143a54 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 24 Oct 2024 02:55:04 +0200 Subject: [PATCH 014/129] RusticErrors for Dump incl Display for NodeType Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/backend/node.rs | 9 ++++++++- crates/core/src/commands/dump.rs | 8 ++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/crates/core/src/backend/node.rs b/crates/core/src/backend/node.rs index 3ff4522e..5127319b 100644 --- a/crates/core/src/backend/node.rs +++ b/crates/core/src/backend/node.rs @@ -122,15 +122,18 @@ pub struct Node { } #[serde_as] -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, strum::Display)] #[serde(tag = "type", rename_all = "lowercase")] /// Types a [`Node`] can have with type-specific additional information pub enum NodeType { /// Node is a regular file + #[strum(to_string = "file")] File, /// Node is a directory + #[strum(to_string = "dir")] Dir, /// Node is a symlink + #[strum(to_string = "symlink:{linktarget}")] Symlink { /// The target of the symlink /// @@ -147,20 +150,24 @@ pub enum NodeType { linktarget_raw: Option>, }, /// Node is a block device file + #[strum(to_string = "dev:{device}")] Dev { #[serde(default)] /// Device id device: u64, }, /// Node is a char device file + #[strum(to_string = "chardev:{device}")] Chardev { #[serde(default)] /// Device id device: u64, }, /// Node is a fifo + #[strum(to_string = "fifo")] Fifo, /// Node is a socket + #[strum(to_string = "socket")] Socket, } diff --git a/crates/core/src/commands/dump.rs b/crates/core/src/commands/dump.rs index 52ccaa1e..44b30f56 100644 --- a/crates/core/src/commands/dump.rs +++ b/crates/core/src/commands/dump.rs @@ -5,6 +5,7 @@ use crate::{ blob::{BlobId, BlobType}, error::RusticResult, repository::{IndexedFull, Repository}, + ErrorKind, RusticError, }; /// Dumps the contents of a file. @@ -31,8 +32,11 @@ pub(crate) fn dump( w: &mut impl Write, ) -> RusticResult<()> { if node.node_type != NodeType::File { - return Err(CommandErrorKind::DumpNotSupported(node.node_type.clone()).into()) - .map_err(|_err| todo!("Error transition")); + return Err(RusticError::new( + ErrorKind::Command, + "Dump is not supported for non-file node types. You could try to use `cat` instead.", + ) + .add_context("node type", node.node_type.to_string())); } for id in node.content.as_ref().unwrap() { From 612ccd1f623fa253317365c498e9587a4c2033b1 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 24 Oct 2024 02:57:45 +0200 Subject: [PATCH 015/129] RusticErrors for forget Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/commands/forget.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/core/src/commands/forget.rs b/crates/core/src/commands/forget.rs index dfe76e71..299ad7bc 100644 --- a/crates/core/src/commands/forget.rs +++ b/crates/core/src/commands/forget.rs @@ -6,7 +6,7 @@ use serde_derive::{Deserialize, Serialize}; use serde_with::{serde_as, skip_serializing_none, DisplayFromStr}; use crate::{ - error::RusticResult, + error::{ErrorKind, RusticError, RusticResult}, progress::ProgressBars, repofile::{ snapshotfile::{SnapshotGroup, SnapshotGroupCriterion, SnapshotId}, @@ -523,7 +523,10 @@ impl KeepOptions { now: DateTime, ) -> RusticResult> { if !self.is_valid() { - return Err(CommandErrorKind::NoKeepOption).map_err(|_err| todo!("Error transition")); + return Err(RusticError::new( + ErrorKind::Command, + "No valid keep options specified, please make sure to specify at least one keep-* option.", + )); } let mut group_keep = self.clone(); From cfaf6de1b36cca01b310ce820b489a75b716de2e Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 24 Oct 2024 03:03:56 +0200 Subject: [PATCH 016/129] RusticErrors for repair Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/commands/repair/index.rs | 11 +++++++---- crates/core/src/commands/repair/snapshots.rs | 7 +++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/crates/core/src/commands/repair/index.rs b/crates/core/src/commands/repair/index.rs index 7c169f44..f5ba72be 100644 --- a/crates/core/src/commands/repair/index.rs +++ b/crates/core/src/commands/repair/index.rs @@ -14,6 +14,7 @@ use crate::{ progress::{Progress, ProgressBars}, repofile::{packfile::PackId, IndexFile, IndexPack, PackHeader, PackHeaderRef}, repository::{Open, Repository}, + RusticError, }; #[cfg_attr(feature = "clap", derive(clap::Parser))] @@ -44,10 +45,12 @@ pub(crate) fn repair_index( dry_run: bool, ) -> RusticResult<()> { if repo.config().append_only == Some(true) { - return Err(CommandErrorKind::NotAllowedWithAppendOnly( - "index repair".to_string(), - )) - .map_err(|_err| todo!("Error transition")); + return Err( + RusticError::new( + ErrorKind::Repository, + "index repair is not allowed in append-only repositories. Please disable append-only mode first, if you know what you are doing.", + ) + ); } let be = repo.dbe(); diff --git a/crates/core/src/commands/repair/snapshots.rs b/crates/core/src/commands/repair/snapshots.rs index 22400743..6b594f99 100644 --- a/crates/core/src/commands/repair/snapshots.rs +++ b/crates/core/src/commands/repair/snapshots.rs @@ -14,7 +14,7 @@ use crate::{ tree::{Tree, TreeId}, BlobId, BlobType, }, - error::RusticResult, + error::{ErrorKind, RusticError, RusticResult}, index::{indexer::Indexer, ReadGlobalIndex, ReadIndex}, progress::ProgressBars, repofile::{snapshotfile::SnapshotId, SnapshotFile, StringList}, @@ -99,7 +99,10 @@ pub(crate) fn repair_snapshots( if opts.delete && config_file.append_only == Some(true) { return Err( - CommandErrorKind::NotAllowedWithAppendOnly("snapshot removal".to_string()).into(), + RusticError::new( + ErrorKind::Repository, + "snapshot removal is not allowed in append-only repositories. Please disable append-only mode first, if you know what you are doing.", + ) ); } From 9d1534b84563652408258444e6cacb0cfe9b5a93 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 24 Oct 2024 03:48:53 +0200 Subject: [PATCH 017/129] RusticErrors for crypto, need std feature for impl Error for aead Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/Cargo.toml | 2 +- crates/core/src/crypto.rs | 8 ++++---- crates/core/src/crypto/aespoly1305.rs | 26 ++++++++++++++++++++------ crates/core/src/error.rs | 2 +- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 52a3f12e..98739b4e 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -58,7 +58,7 @@ pariter = "0.5.1" rayon = "1.10.0" # crypto -aes256ctr_poly1305aes = "0.2.0" +aes256ctr_poly1305aes = { version = "0.2.0", features = ["std"] } rand = "0.8.5" scrypt = { version = "0.11.0", default-features = false } diff --git a/crates/core/src/crypto.rs b/crates/core/src/crypto.rs index 37a2a120..4d1f6d81 100644 --- a/crates/core/src/crypto.rs +++ b/crates/core/src/crypto.rs @@ -1,3 +1,5 @@ +use crate::RusticResult; + pub(crate) mod aespoly1305; pub(crate) mod hasher; @@ -13,8 +15,6 @@ pub enum CryptoErrorKind { CryptoKeyTooShort, } -pub(crate) type CryptoResult = Result; - /// A trait for encrypting and decrypting data. pub trait CryptoKey: Clone + Copy + Sized + Send + Sync + 'static { /// Decrypt the given data. @@ -26,7 +26,7 @@ pub trait CryptoKey: Clone + Copy + Sized + Send + Sync + 'static { /// # Returns /// /// A vector containing the decrypted data. - fn decrypt_data(&self, data: &[u8]) -> CryptoResult>; + fn decrypt_data(&self, data: &[u8]) -> RusticResult>; /// Encrypt the given data. /// @@ -37,5 +37,5 @@ pub trait CryptoKey: Clone + Copy + Sized + Send + Sync + 'static { /// # Returns /// /// A vector containing the encrypted data. - fn encrypt_data(&self, data: &[u8]) -> CryptoResult>; + fn encrypt_data(&self, data: &[u8]) -> RusticResult>; } diff --git a/crates/core/src/crypto/aespoly1305.rs b/crates/core/src/crypto/aespoly1305.rs index 5802d40c..e484bf65 100644 --- a/crates/core/src/crypto/aespoly1305.rs +++ b/crates/core/src/crypto/aespoly1305.rs @@ -4,7 +4,7 @@ use aes256ctr_poly1305aes::{ }; use rand::{thread_rng, RngCore}; -use crate::crypto::{CryptoErrorKind, CryptoKey, CryptoResult}; +use crate::{crypto::CryptoKey, error::RusticResult, ErrorKind, RusticError}; pub(crate) type Nonce = aead::Nonce; pub(crate) type AeadKey = aes256ctr_poly1305aes::Key; @@ -82,15 +82,25 @@ impl CryptoKey for Key { /// # Errors /// /// If the MAC couldn't be checked. - fn decrypt_data(&self, data: &[u8]) -> CryptoResult> { + fn decrypt_data(&self, data: &[u8]) -> RusticResult> { if data.len() < 16 { - return Err(CryptoErrorKind::CryptoKeyTooShort)?; + return Err(RusticError::new( + ErrorKind::Cryptography, + "Data is too short (less than 16 bytes), cannot decrypt.", + ))?; } let nonce = Nonce::from_slice(&data[0..16]); Aes256CtrPoly1305Aes::new(&self.0) .decrypt(nonce, &data[16..]) - .map_err(|err| CryptoErrorKind::DataDecryptionFailed(err)) + .map_err(|err| { + RusticError::new( + ErrorKind::Cryptography, + "Data decryption failed, MAC check failed.", + ) + .add_context("nonce", format!("{:?}", nonce)) + .source(err.into()) + }) } /// Returns the encrypted+MACed data from the given data. @@ -102,7 +112,7 @@ impl CryptoKey for Key { /// # Errors /// /// If the data could not be encrypted. - fn encrypt_data(&self, data: &[u8]) -> CryptoResult> { + fn encrypt_data(&self, data: &[u8]) -> RusticResult> { let mut nonce = Nonce::default(); thread_rng().fill_bytes(&mut nonce); @@ -111,7 +121,11 @@ impl CryptoKey for Key { res.extend_from_slice(data); let tag = Aes256CtrPoly1305Aes::new(&self.0) .encrypt_in_place_detached(&nonce, &[], &mut res[16..]) - .map_err(CryptoErrorKind::DataEncryptionFailed)?; + .map_err(|err| { + RusticError::new(ErrorKind::Cryptography, "Data encryption failed.") + .add_context("nonce", format!("{:?}", nonce)) + .source(err.into()) + })?; res.extend_from_slice(&tag); Ok(res) } diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index a6992b88..204912e1 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -219,7 +219,7 @@ pub enum ErrorKind { /// Blob Error Blob, /// Crypto Error - Crypto, + Cryptography, /// Compression Error Compression, /// Parsing Error From 12d540b098848cf3eb2cfcc09dff3b83734f4cfe Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 24 Oct 2024 03:59:32 +0200 Subject: [PATCH 018/129] give error code to data decryption error for later usage when matching possible repository keys Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/crypto/aespoly1305.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/core/src/crypto/aespoly1305.rs b/crates/core/src/crypto/aespoly1305.rs index e484bf65..be2b68cb 100644 --- a/crates/core/src/crypto/aespoly1305.rs +++ b/crates/core/src/crypto/aespoly1305.rs @@ -100,6 +100,7 @@ impl CryptoKey for Key { ) .add_context("nonce", format!("{:?}", nonce)) .source(err.into()) + .code("C001".into()) }) } From 5192bdd31215b9982a0d18953fa578a6ad62da1e Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 24 Oct 2024 04:00:18 +0200 Subject: [PATCH 019/129] RusticErrors for Cryptographic stuff Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/crypto.rs | 12 -------- crates/core/src/error.rs | 4 +++ crates/core/src/repofile/keyfile.rs | 43 ++++++++++++++--------------- 3 files changed, 25 insertions(+), 34 deletions(-) diff --git a/crates/core/src/crypto.rs b/crates/core/src/crypto.rs index 4d1f6d81..89e88554 100644 --- a/crates/core/src/crypto.rs +++ b/crates/core/src/crypto.rs @@ -3,18 +3,6 @@ use crate::RusticResult; pub(crate) mod aespoly1305; pub(crate) mod hasher; -/// [`CryptoErrorKind`] describes the errors that can happen while dealing with Cryptographic functions -#[derive(thiserror::Error, Debug, displaydoc::Display, Copy, Clone)] -#[non_exhaustive] -pub enum CryptoErrorKind { - /// data decryption failed: `{0:?}` - DataDecryptionFailed(aes256ctr_poly1305aes::aead::Error), - /// data encryption failed: `{0:?}` - DataEncryptionFailed(aes256ctr_poly1305aes::aead::Error), - /// crypto key too short - CryptoKeyTooShort, -} - /// A trait for encrypting and decrypting data. pub trait CryptoKey: Clone + Copy + Sized + Send + Sync + 'static { /// Decrypt the given data. diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 204912e1..d522470e 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -138,6 +138,10 @@ impl RusticError { } } + pub fn is_code(&self, code: &str) -> bool { + self.code.as_ref().map_or(false, |c| c.as_str() == code) + } + /// Expose the inner error kind. /// /// This is useful for matching on the error kind. diff --git a/crates/core/src/repofile/keyfile.rs b/crates/core/src/repofile/keyfile.rs index 0abcf47c..605799f0 100644 --- a/crates/core/src/repofile/keyfile.rs +++ b/crates/core/src/repofile/keyfile.rs @@ -6,17 +6,15 @@ use serde_with::{base64::Base64, serde_as, skip_serializing_none}; use crate::{ backend::{FileType, ReadBackend}, - crypto::{aespoly1305::Key, CryptoErrorKind, CryptoKey}, + crypto::{aespoly1305::Key, CryptoKey}, error::RusticResult, - impl_repoid, + impl_repoid, ErrorKind, RusticError, }; /// [`KeyFileErrorKind`] describes the errors that can be returned for `KeyFile`s #[derive(thiserror::Error, Debug, displaydoc::Display)] #[non_exhaustive] pub enum KeyFileErrorKind { - /// no suitable key found! - NoSuitableKeyFound, /// listing KeyFiles failed ListingKeyFilesFailed, /// couldn't get KeyFile from backend @@ -34,8 +32,6 @@ pub enum KeyFileErrorKind { OutputLengthInvalid(scrypt::errors::InvalidOutputLen), /// invalid scrypt parameters: `{0:?}` InvalidSCryptParameters(scrypt::errors::InvalidParams), - /// Could not get key from decrypt data: `{key:?}` : `{source}` - CouldNotGetKeyFromDecryptData { key: Key, source: CryptoErrorKind }, /// deserializing master key from slice failed: `{source}` DeserializingMasterKeyFromSliceFailed { source: serde_json::Error }, /// conversion from {from} to {to} failed for {x} : {source} @@ -151,18 +147,19 @@ impl KeyFile { /// /// [`KeyFileErrorKind::DeserializingFromSliceFailed`]: crate::error::KeyFileErrorKind::DeserializingFromSliceFailed pub fn key_from_data(&self, key: &Key) -> RusticResult { - let dec_data = key - .decrypt_data(&self.data) - .map_err(|err| KeyFileErrorKind::CouldNotGetKeyFromDecryptData { - key: key.clone(), - source: err, - }) - .map_err(|_err| todo!("Error transition"))?; - - Ok(serde_json::from_slice::(&dec_data) - .map_err(|err| KeyFileErrorKind::DeserializingMasterKeyFromSliceFailed { source: err }) - .map_err(|_err| todo!("Error transition"))? - .key()) + let dec_data = key.decrypt_data(&self.data)?; + + let key = serde_json::from_slice::(&dec_data) + .map_err(|err| { + RusticError::new( + ErrorKind::Key, + "Deserializing master key from slice failed. Please check the key file.", + ) + .source(err.into()) + })? + .key(); + + Ok(key) } /// Extract a key from the data of the [`KeyFile`] using the key @@ -406,12 +403,14 @@ pub(crate) fn find_key_in_backend( for id in be.list(FileType::Key)? { match key_from_backend(be, &id.into(), passwd) { Ok(key) => return Ok(key), - // TODO: We get a RusticError here and we need to determine, if we have a WrongKey error - // TODO: We should probably implement something for that on RusticError or use a variant for this - Err(KeyFileErrorKind::DataDecryptionFailed(_)) => continue, + Err(err) if err.is_code("C001") => continue, err => return err, } } - Err(KeyFileErrorKind::NoSuitableKeyFound).map_err(|_err| todo!("Error transition")) + + Err(RusticError::new( + ErrorKind::Key, + "No suitable key found for the given password. Please check your password and try again.", + )) } } From 4563edc8ccb264c14b608b03cb80ec529a63a498 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 24 Oct 2024 04:02:06 +0200 Subject: [PATCH 020/129] revert check to before draft error handling Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/repository.rs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/crates/core/src/repository.rs b/crates/core/src/repository.rs index 15a0a196..21896f11 100644 --- a/crates/core/src/repository.rs +++ b/crates/core/src/repository.rs @@ -9,10 +9,7 @@ use std::{ io::{BufRead, BufReader, Write}, path::{Path, PathBuf}, process::{Command, Stdio}, - sync::{ - atomic::{AtomicBool, Ordering as AtomicOrdering}, - Arc, - }, + sync::Arc, }; use bytes::Bytes; @@ -59,7 +56,7 @@ use crate::{ progress::{NoProgressBars, Progress, ProgressBars}, repofile::{ configfile::ConfigId, - keyfile::{find_key_in_backend, KeyFileErrorKind}, + keyfile::find_key_in_backend, packfile::PackId, snapshotfile::{SnapshotGroup, SnapshotGroupCriterion, SnapshotId}, ConfigFile, KeyId, PathList, RepoFile, RepoId, SnapshotFile, SnapshotSummary, Tree, @@ -1146,13 +1143,9 @@ impl Repository { .map(|snap| snap.tree) .collect(); - let errors = check_repository(self, opts, trees, err_send)?; + check_repository(self, opts, trees)?; - if errors { - Err(CommandErrorKind::CheckFailed.into()).map_err(|_err| todo!("Error transition")) - } else { - Ok(()) - } + Ok(()) } /// Check the repository and given trees for errors or inconsistencies From 546b383739da06430b7c78a2636e4b9a3767049f Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 24 Oct 2024 04:36:08 +0200 Subject: [PATCH 021/129] RusticErrors for repository Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/repository.rs | 155 +++++++++++++++++++++++----------- 1 file changed, 106 insertions(+), 49 deletions(-) diff --git a/crates/core/src/repository.rs b/crates/core/src/repository.rs index 21896f11..517b248c 100644 --- a/crates/core/src/repository.rs +++ b/crates/core/src/repository.rs @@ -63,7 +63,7 @@ use crate::{ }, repository::warm_up::{warm_up, warm_up_wait}, vfs::OpenFile, - RepositoryBackends, + RepositoryBackends, RusticError, }; #[cfg(feature = "clap")] @@ -187,22 +187,33 @@ impl RepositoryOptions { match (&self.password, &self.password_file, &self.password_command) { (Some(pwd), _, _) => Ok(Some(pwd.clone())), (_, Some(file), _) => { - let mut file = - BufReader::new(File::open(file).map_err(ErrorKind::OpeningPasswordFileFailed)?); + let mut file = BufReader::new(File::open(file).map_err(|err| { + RusticError::new( + ErrorKind::Password, + "Opening password file failed. Is the path correct?", + ) + .add_context("path", file.display().to_string()) + .source(err.into()) + })?); Ok(Some(read_password_from_reader(&mut file)?)) } (_, _, Some(command)) if command.is_set() => { debug!("commands: {command:?}"); - let command = Command::new(command.command()) + let run_command = Command::new(command.command()) .args(command.args()) .stdout(Stdio::piped()) .spawn(); - let process = match command { + let process = match run_command { Ok(process) => process, Err(err) => { error!("password-command could not be executed: {}", err); - return Err(ErrorKind::PasswordCommandExecutionFailed.into()); + return Err(RusticError::new( + ErrorKind::Password, + "Password command could not be executed.", + ) + .add_context("command", command.to_string()) + .source(err.into())); } }; @@ -210,7 +221,12 @@ impl RepositoryOptions { Ok(output) => output, Err(err) => { error!("error reading output from password-command: {}", err); - return Err(ErrorKind::ReadingPasswordFromCommandFailed.into()); + return Err(RusticError::new( + ErrorKind::Password, + "Error reading output from password command.", + ) + .add_context("command", command.to_string()) + .source(err.into())); } }; @@ -221,14 +237,16 @@ impl RepositoryOptions { None => "was terminated".into(), }; error!("password-command {s}"); - return Err(ErrorKind::PasswordCommandExecutionFailed.into()); + return Err(RusticError::new( + ErrorKind::Password, + "Password command did not exit successfully.", + ) + .add_context("command", command.to_string()) + .add_context("status", s)); } let mut pwd = BufReader::new(&*output.stdout); - Ok(Some(match read_password_from_reader(&mut pwd) { - Ok(val) => val, - Err(_) => return Err(ErrorKind::ReadingPasswordFromCommandFailed.into()), - })) + Ok(Some(read_password_from_reader(&mut pwd)?)) } (None, None, _) => Ok(None), } @@ -243,14 +261,13 @@ impl RepositoryOptions { /// /// # Errors /// -/// * [`RusticErrorKind::ReadingPasswordFromReaderFailed`] - If reading the password failed -/// -/// [`RusticErrorKind::ReadingPasswordFromReaderFailed`]: crate::error::RusticErrorKind::ReadingPasswordFromReaderFailed pub fn read_password_from_reader(file: &mut impl BufRead) -> RusticResult { let mut password = String::new(); - _ = file - .read_line(&mut password) - .map_err(ErrorKind::ReadingPasswordFromReaderFailed)?; + _ = file.read_line(&mut password).map_err(|err| { + RusticError::new(ErrorKind::Password, "Reading password from reader failed. Is the file empty? Please check the file and the password.") + .add_context("password", password.clone()) + .source(err.into()) + })?; // Remove the \n from the line if present if password.ends_with('\n') { @@ -348,7 +365,11 @@ impl

Repository { if let Some(warm_up) = &opts.warm_up_command { if warm_up.args().iter().all(|c| !c.contains("%id")) { - return Err(ErrorKind::NoIDSpecified.into()); + return Err(RusticError::new( + ErrorKind::Command, + "No `%id` specified in warm-up command. Please specify `%id` in the command.", + ) + .add_context("command", warm_up.to_string())); } info!("using warm-up command {warm_up}"); } @@ -413,15 +434,16 @@ impl Repository { /// [`RusticErrorKind::ListingRepositoryConfigFileFailed`]: crate::error::RusticErrorKind::ListingRepositoryConfigFileFailed /// [`RusticErrorKind::MoreThanOneRepositoryConfig`]: crate::error::RusticErrorKind::MoreThanOneRepositoryConfig pub fn config_id(&self) -> RusticResult> { - let config_ids = self - .be - .list(FileType::Config) - .map_err(|_| ErrorKind::ListingRepositoryConfigFileFailed)?; + let config_ids = self.be.list(FileType::Config)?; match config_ids.len() { 1 => Ok(Some(ConfigId::from(config_ids[0]))), 0 => Ok(None), - _ => Err(ErrorKind::MoreThanOneRepositoryConfig(self.name.clone()).into()), + _ => Err(RusticError::new( + ErrorKind::Config, + "More than one repository found. Please check the config file.", + ) + .add_context("name", self.name.clone())), } } @@ -461,7 +483,13 @@ impl Repository { /// [`RusticErrorKind::ListingRepositoryConfigFileFailed`]: crate::error::RusticErrorKind::ListingRepositoryConfigFileFailed /// [`RusticErrorKind::MoreThanOneRepositoryConfig`]: crate::error::RusticErrorKind::MoreThanOneRepositoryConfig pub fn open(self) -> RusticResult> { - let password = self.password()?.ok_or(ErrorKind::NoPasswordGiven)?; + let password = self.password()?.ok_or({ + RusticError::new( + ErrorKind::Password, + "No password given, or Password was empty. Please specify a valid password.", + ) + })?; + self.open_with_password(&password) } @@ -489,10 +517,13 @@ impl Repository { /// [`RusticErrorKind::ListingRepositoryConfigFileFailed`]: crate::error::RusticErrorKind::ListingRepositoryConfigFileFailed /// [`RusticErrorKind::MoreThanOneRepositoryConfig`]: crate::error::RusticErrorKind::MoreThanOneRepositoryConfig pub fn open_with_password(self, password: &str) -> RusticResult> { - let config_id = self - .config_id()? - .ok_or(ErrorKind::NoRepositoryConfigFound(self.name.clone())) - .map_err(|_err| todo!("Error transition"))?; + let config_id = self.config_id()?.ok_or( + RusticError::new( + ErrorKind::Config, + "No repository config file found. Please check the repository.", + ) + .add_context("name", self.name.clone()), + )?; if let Some(be_hot) = &self.be_hot { let mut keys = self.be.list_with_size(FileType::Key)?; @@ -500,8 +531,11 @@ impl Repository { let mut hot_keys = be_hot.list_with_size(FileType::Key)?; hot_keys.sort_unstable_by_key(|key| key.0); if keys != hot_keys { - return Err(ErrorKind::KeysDontMatchForRepositories(self.name).into()) - .map_err(|_err| todo!("Error transition")); + return Err(RusticError::new( + ErrorKind::Key, + "Keys of hot and cold repositories don't match. Please check the keys.", + ) + .add_context("name", self.name.clone())); } } @@ -547,7 +581,14 @@ impl Repository { key_opts: &KeyOptions, config_opts: &ConfigOptions, ) -> RusticResult> { - let password = self.password()?.ok_or(ErrorKind::NoPasswordGiven)?; + let password = self.password()?.ok_or( + RusticError::new( + ErrorKind::Password, + "No password given, or Password was empty. Please specify a valid password.", + ) + .add_context("name", self.name.clone()), + )?; + self.init_with_password(&password, key_opts, config_opts) } @@ -581,8 +622,13 @@ impl Repository { config_opts: &ConfigOptions, ) -> RusticResult> { if self.config_id()?.is_some() { - return Err(ErrorKind::ConfigFileExists.into()); + return Err(RusticError::new( + ErrorKind::Config, + "Config file already exists. Please check the repository.", + ) + .add_context("name", self.name.clone())); } + let (key, config) = commands::init::init(&self, pass, key_opts, config_opts)?; self.open_raw(key, config) @@ -629,15 +675,22 @@ impl Repository { /// /// # Errors /// - /// * [`RusticErrorKind::HotRepositoryFlagMissing`] - If the config file has `is_hot` set to `true` but the repository is not hot - /// * [`RusticErrorKind::IsNotHotRepository`] - If the config file has `is_hot` set to `false` but the repository is hot - /// - /// [`RusticErrorKind::HotRepositoryFlagMissing`]: crate::error::RusticErrorKind::HotRepositoryFlagMissing - /// [`RusticErrorKind::IsNotHotRepository`]: crate::error::RusticErrorKind::IsNotHotRepository + /// * If the config file has `is_hot` set to `true` but the repository is not hot + /// * If the config file has `is_hot` set to `false` but the repository is hot fn open_raw(mut self, key: Key, config: ConfigFile) -> RusticResult> { match (config.is_hot == Some(true), self.be_hot.is_some()) { - (true, false) => return Err(ErrorKind::HotRepositoryFlagMissing.into()), - (false, true) => return Err(ErrorKind::IsNotHotRepository.into()), + (true, false) => return Err( + RusticError::new( + ErrorKind::Repository, + "The given repository is a hot repository! Please use `--repo-hot` in combination with the normal repo. Aborting.", + ) + ), + (false, true) => return Err( + RusticError::new( + ErrorKind::Repository, + "The given repository is not a hot repository! Aborting.", + ) + ), _ => {} } @@ -1096,7 +1149,10 @@ impl Repository { pub fn delete_snapshots(&self, ids: &[SnapshotId]) -> RusticResult<()> { if self.config().append_only == Some(true) { return Err( - ErrorKind::NotAllowedWithAppendOnly("snapshots removal".to_string()).into(), + RusticError::new( + ErrorKind::Repository, + "Repository is in append-only mode and snapshots cannot be deleted from it. Aborting.", + ) ); } let p = self.pb.progress_counter("removing snapshots..."); @@ -1568,16 +1624,17 @@ impl Repository { /// /// # Errors /// - /// * [`RusticErrorKind::IdNotFound`] - If the id is not found in the index - /// - /// [`RusticErrorKind::IdNotFound`]: crate::error::RusticErrorKind::IdNotFound + /// * If the id is not found in the index pub fn get_index_entry(&self, id: &T) -> RusticResult { let blob_id: BlobId = (*id).into(); - let ie = self - .index() - .get_id(T::TYPE, &blob_id) - .ok_or_else(|| ErrorKind::IdNotFound(blob_id)) - .map_err(|_err| todo!("Error transition"))?; + let ie = self.index().get_id(T::TYPE, &blob_id).ok_or_else(|| { + RusticError::new( + ErrorKind::Index, + "BlobID not found in index, but should be there. This is a bug. Please report it.", + ) + .add_context("blob id", blob_id.to_string()) + })?; + Ok(ie) } From 98a1c4ac71f00c4d1106fc78803d6197265822f3 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 24 Oct 2024 04:50:54 +0200 Subject: [PATCH 022/129] optimize error handling in local hackend Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/local.rs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/crates/backend/src/local.rs b/crates/backend/src/local.rs index 846449e7..682a00bc 100644 --- a/crates/backend/src/local.rs +++ b/crates/backend/src/local.rs @@ -427,36 +427,33 @@ impl WriteBackend for LocalBackend { trace!("creating repo at {:?}", self.path); fs::create_dir_all(&self.path).map_err(|err| { RusticError::new( - ErrorKind::Backend, + ErrorKind::Io, "Failed to create the directory. Please check the path and try again.", ) - .add_context("path", self.path.to_string_lossy()) + .add_context("path", self.path.display().to_string()) .source(err.into()) })?; for tpe in ALL_FILE_TYPES { - fs::create_dir_all(self.path.join(tpe.dirname())).map_err(|err| { + let path = self.path.join(tpe.dirname()); + fs::create_dir_all(path).map_err(|err| { RusticError::new( - ErrorKind::Backend, + ErrorKind::Io, "Failed to create the directory. Please check the path and try again.", ) - .add_context("path", self.path.join(tpe.dirname()).to_string_lossy()) + .add_context("path", path.display().to_string()) .source(err.into()) })?; } + for i in 0u8..=255 { - fs::create_dir_all(self.path.join("data").join(hex::encode([i]))).map_err(|err| { + let path = self.path.join("data").join(hex::encode([i])); + fs::create_dir_all(path).map_err(|err| { RusticError::new( - ErrorKind::Backend, + ErrorKind::Io, "Failed to create the directory. Please check the path and try again.", ) - .add_context( - "path", - self.path - .join("data") - .join(hex::encode([i])) - .to_string_lossy(), - ) + .add_context("path", path.display().to_string()) .source(err.into()) })?; } From 4527c21a36cb7364fce4d75491485eda7d69a5fa Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 24 Oct 2024 04:51:13 +0200 Subject: [PATCH 023/129] RusticErrors in restore Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/commands/restore.rs | 65 +++++++++++++++++------------ crates/core/src/error.rs | 2 + 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/crates/core/src/commands/restore.rs b/crates/core/src/commands/restore.rs index fcbb863d..a87f82d8 100644 --- a/crates/core/src/commands/restore.rs +++ b/crates/core/src/commands/restore.rs @@ -27,6 +27,7 @@ use crate::{ progress::{Progress, ProgressBars}, repofile::packfile::PackId, repository::{IndexedFull, IndexedTree, Open, Repository}, + ErrorKind, RusticError, }; pub(crate) mod constants { @@ -224,9 +225,13 @@ pub(crate) fn collect_and_prepare( if !dry_run { dest.create_dir(path) .map_err(|err| { - CommandErrorKind::ErrorCreating(path.clone(), Box::new(err)) - }) - .map_err(|_err| todo!("Error transition"))?; + RusticError::new( + ErrorKind::Io, + "Failed to create the directory. Please check the path and try again.", + ) + .add_context("path", path.display().to_string()) + .source(err.into()) + })?; } } } @@ -234,12 +239,7 @@ pub(crate) fn collect_and_prepare( // collect blobs needed for restoring match ( exists, - restore_infos - .add_file(dest, node, path.clone(), repo, opts.verify_existing) - .map_err(|err| { - CommandErrorKind::ErrorCollecting(path.clone(), Box::new(err)) - }) - .map_err(|_err| todo!("Error transition"))?, + restore_infos.add_file(dest, node, path.clone(), repo, opts.verify_existing)?, ) { // Note that exists = false and Existing or Verified can happen if the file is changed between scanning the dir // and calling add_file. So we don't care about exists but trust add_file here. @@ -452,9 +452,14 @@ fn restore_contents( for (i, size) in file_lengths.iter().enumerate() { if *size == 0 { let path = &filenames[i]; - dest.set_length(path, *size) - .map_err(|err| CommandErrorKind::ErrorSettingLength(path.clone(), Box::new(err))) - .map_err(|_err| todo!("Error transition"))?; + dest.set_length(path, *size).map_err(|err| { + RusticError::new( + ErrorKind::Io, + "Failed to set the length of the file. Please check the path and try again.", + ) + .add_context("path", path.display().to_string()) + .source(err.into()) + })?; } } @@ -495,11 +500,20 @@ fn restore_contents( }) .collect(); + let threads = constants::MAX_READER_THREADS_NUM; + let pool = ThreadPoolBuilder::new() - .num_threads(constants::MAX_READER_THREADS_NUM) + .num_threads(threads) .build() - .map_err(CommandErrorKind::FromRayonError) - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| { + RusticError::new( + ErrorKind::Multithreading, + "Failed to create the thread pool. Please try again.", + ) + .add_context("num threads", threads.to_string()) + .source(err.into()) + })?; + pool.in_place_scope(|s| { for (pack, offset, length, from_file, name_dests) in blobs { let p = &p; @@ -542,15 +556,7 @@ fn restore_contents( let mut sizes_guard = sizes.lock().unwrap(); let filesize = sizes_guard[file_idx]; if filesize > 0 { - dest.set_length(path, filesize) - .map_err(|err| { - CommandErrorKind::ErrorSettingLength( - path.clone(), - Box::new(err), - ) - }) - .map_err(|_err| todo!("Error transition")) - .unwrap(); + dest.set_length(path, filesize).unwrap(); sizes_guard[file_idx] = 0; } drop(sizes_guard); @@ -714,9 +720,14 @@ impl RestorePlan { }; let length = bl.data_length(); - let usize_length = usize::try_from(length) - .map_err(CommandErrorKind::ConversionFromIntFailed) - .map_err(|_err| todo!("Error transition"))?; + let usize_length = usize::try_from(length).map_err(|err| { + RusticError::new( + ErrorKind::Conversion, + "Failed to convert the length to usize. Please try again.", + ) + .add_context("length", length.to_string()) + .source(err.into()) + })?; let matches = open_file .as_mut() diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index d522470e..640a5f60 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -234,6 +234,8 @@ pub enum ErrorKind { Permission, /// Polynomial Error Polynomial, + /// Multithreading Error + Multithreading, // /// The repository password is incorrect. Please try again. // IncorrectRepositoryPassword, // /// No repository given. Please use the --repository option. From dee0dfa57e05d894ba939fae71f5bb533702486e Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 24 Oct 2024 05:53:12 +0200 Subject: [PATCH 024/129] more RusticErrors Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- Cargo.lock | 1 + crates/backend/Cargo.toml | 1 + crates/backend/src/choose.rs | 13 +-- crates/backend/src/local.rs | 92 ++++++++++++--------- crates/backend/src/util.rs | 16 ++-- crates/core/src/commands/prune.rs | 59 +++++++++---- crates/core/src/crypto/aespoly1305.rs | 4 +- crates/core/src/error.rs | 35 ++++++-- crates/core/src/lib.rs | 5 +- crates/core/src/repository.rs | 7 +- crates/core/src/repository/command_input.rs | 2 +- crates/testing/src/backend.rs | 30 ++++--- 12 files changed, 164 insertions(+), 101 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 71745aa7..0f5d8519 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3270,6 +3270,7 @@ name = "rustic_backend" version = "0.4.1" dependencies = [ "aho-corasick", + "anyhow", "backoff", "bytes", "bytesize", diff --git a/crates/backend/Cargo.toml b/crates/backend/Cargo.toml index 25b70d8b..87a00fd6 100644 --- a/crates/backend/Cargo.toml +++ b/crates/backend/Cargo.toml @@ -93,6 +93,7 @@ opendal = { version = "0.50.0", features = ["services-b2", "services-sftp", "ser opendal = { version = "0.50.0", features = ["services-b2", "services-swift", "services-azblob", "services-azdls", "services-cos", "services-fs", "services-ftp", "services-dropbox", "services-gdrive", "services-gcs", "services-ghac", "services-http", "services-ipmfs", "services-memory", "services-obs", "services-onedrive", "services-oss", "services-s3", "services-webdav", "services-webhdfs", "services-azfile", "layers-blocking", "layers-throttle"], optional = true } [dev-dependencies] +anyhow = { workspace = true } rstest = { workspace = true } toml = "0.8.19" diff --git a/crates/backend/src/choose.rs b/crates/backend/src/choose.rs index 0419c53f..685def7f 100644 --- a/crates/backend/src/choose.rs +++ b/crates/backend/src/choose.rs @@ -1,6 +1,6 @@ //! This module contains [`BackendOptions`] and helpers to choose a backend from a given url. use derive_setters::Setters; -use rustic_core::RusticError; +use rustic_core::{ErrorKind, RusticError}; use std::{collections::HashMap, sync::Arc}; use strum_macros::{Display, EnumString}; @@ -24,13 +24,6 @@ use crate::rest::RestBackend; #[cfg(feature = "clap")] use clap::ValueHint; -/// [`ChooseBackendErrorKind`] describes the errors that can be returned by the choose backend -#[derive(thiserror::Error, Debug, displaydoc::Display)] -#[non_exhaustive] -pub enum ChooseBackendErrorKind {} - -pub(crate) type ChooseBackendResult = Result; - /// Options for a backend. #[cfg_attr(feature = "clap", derive(clap::Parser))] #[cfg_attr(feature = "merge", derive(conflate::Merge))] @@ -88,7 +81,7 @@ impl BackendOptions { .get_backend(self.repository.as_ref(), options)? .ok_or_else(|| { RusticError::new( - rustic_core::ErrorKind::Backend, + ErrorKind::Backend, "No repository given. Please make sure, that you have set the repository.", ) })?; @@ -128,7 +121,7 @@ impl BackendOptions { ErrorKind::Backend, "Could not load the backend. Please check the given backend and try again.", ) - .add_context("name", be_type) + .add_context("name", be_type.to_string()) .source(err.into()) }) }) diff --git a/crates/backend/src/local.rs b/crates/backend/src/local.rs index 682a00bc..bf6cade7 100644 --- a/crates/backend/src/local.rs +++ b/crates/backend/src/local.rs @@ -1,8 +1,9 @@ use std::{ fs::{self, File}, io::{Read, Seek, SeekFrom, Write}, + num::TryFromIntError, path::{Path, PathBuf}, - process::Command, + process::{Command, ExitStatus}, }; use aho_corasick::AhoCorasick; @@ -11,8 +12,8 @@ use log::{debug, trace, warn}; use walkdir::WalkDir; use rustic_core::{ - CommandInput, ErrorKind, FileType, Id, ReadBackend, RusticError, RusticResult, WriteBackend, - ALL_FILE_TYPES, + CommandInput, CommandInputErrorKind, ErrorKind, FileType, Id, ReadBackend, RusticError, + RusticResult, WriteBackend, ALL_FILE_TYPES, }; /// [`LocalBackendErrorKind`] describes the errors that can be returned by an action on the filesystem in Backends @@ -93,7 +94,10 @@ impl LocalBackend { /// /// * `post-create-command` - The command to call after a file was created. /// * `post-delete-command` - The command to call after a file was deleted. - pub fn new(path: impl AsRef, options: impl IntoIterator) -> Self { + pub fn new( + path: impl AsRef, + options: impl IntoIterator, + ) -> RusticResult { let path = path.as_ref().into(); let mut post_create_command = None; let mut post_delete_command = None; @@ -111,11 +115,11 @@ impl LocalBackend { } } - Self { + Ok(Self { path, post_create_command, post_delete_command, - } + }) } /// Path to the given file type and id. @@ -175,36 +179,39 @@ impl LocalBackend { debug!("calling {actual_command}..."); - let command: CommandInput = actual_command.parse().map_err(|err| { - RusticError::new( - ErrorKind::Backend, - "Failed to parse command input. This is a bug. Please report it.", - ) - .add_context("command", actual_command) - .add_context("replacement", replace_with.join(", ")) - .source(err.into()) - })?; + let command: CommandInput = + actual_command + .parse() + .map_err(|err: CommandInputErrorKind| { + RusticError::new( + ErrorKind::Parsing, + "Failed to parse command input. This is a bug. Please report it.", + ) + .add_context("command", actual_command) + .add_context("replacement", replace_with.join(", ")) + .source(err.into()) + })?; let status = Command::new(command.command()) .args(command.args()) .status() .map_err(|err| { RusticError::new( - ErrorKind::Backend, + ErrorKind::Command, "Failed to execute command. Please check the command and try again.", ) - .add_context("command", command) + .add_context("command", command.to_string()) .source(err.into()) })?; if !status.success() { - return Err(LocalBackendErrorKind::CommandNotSuccessful { - file_name: replace_with[0].to_owned(), - file_type: replace_with[1].to_owned(), - id: replace_with[2].to_owned(), - status, - } - .into()); + return Err( + RusticError::new(ErrorKind::Command, "Command was not successful.") + .add_context("file_name", replace_with[0]) + .add_context("file_type", replace_with[1]) + .add_context("id", replace_with[2]) + .add_context("status", status.to_string()), + ); } Ok(()) } @@ -281,12 +288,12 @@ impl ReadBackend for LocalBackend { )? .len() .try_into() - .map_err(|err| + .map_err(|err: TryFromIntError| RusticError::new( ErrorKind::Backend, "Failed to convert file length to u32. This is a bug. Please report it.", ) - .add_context("length", path.metadata().unwrap().len()) + .add_context("length", path.metadata().unwrap().len().to_string()) .source(err.into()) )?, )] @@ -314,12 +321,12 @@ impl ReadBackend for LocalBackend { ? .len() .try_into() - .map_err(|err| + .map_err(|err: TryFromIntError| RusticError::new( ErrorKind::Backend, "Failed to convert file length to u32. This is a bug. Please report it.", ) - .add_context("length", e.metadata().unwrap().len()) + .add_context("length", e.metadata().unwrap().len().to_string()) .source(err.into()) )?, )) @@ -390,27 +397,32 @@ impl ReadBackend for LocalBackend { "Failed to seek to the position in the file. Please check the file and try again.", ) .add_context("path", self.path(tpe, id).to_string_lossy()) - .add_context("offset", offset) + .add_context("offset", offset.to_string()) .source(err.into()) })?; + let mut vec = vec![ 0; - length.try_into().map_err(|err| RusticError::new( - ErrorKind::Backend, - "Failed to convert length to u64. This is a bug. Please report it.", - ) - .add_context("length", length) - .source(err.into()))? + length + .try_into() + .map_err(|err: TryFromIntError| RusticError::new( + ErrorKind::Backend, + "Failed to convert length to u64. This is a bug. Please report it.", + ) + .add_context("length", length.to_string()) + .source(err.into()))? ]; + file.read_exact(&mut vec).map_err(|err| { RusticError::new( ErrorKind::Backend, "Failed to read the exact length of the file. Please check the file and try again.", ) .add_context("path", self.path(tpe, id).to_string_lossy()) - .add_context("length", length) + .add_context("length", length.to_string()) .source(err.into()) })?; + Ok(vec.into()) } } @@ -436,7 +448,7 @@ impl WriteBackend for LocalBackend { for tpe in ALL_FILE_TYPES { let path = self.path.join(tpe.dirname()); - fs::create_dir_all(path).map_err(|err| { + fs::create_dir_all(path.clone()).map_err(|err| { RusticError::new( ErrorKind::Io, "Failed to create the directory. Please check the path and try again.", @@ -448,7 +460,7 @@ impl WriteBackend for LocalBackend { for i in 0u8..=255 { let path = self.path.join("data").join(hex::encode([i])); - fs::create_dir_all(path).map_err(|err| { + fs::create_dir_all(path.clone()).map_err(|err| { RusticError::new( ErrorKind::Io, "Failed to create the directory. Please check the path and try again.", @@ -493,12 +505,12 @@ impl WriteBackend for LocalBackend { .add_context("path", filename.to_string_lossy()) .source(err.into()) })?; - file.set_len(buf.len().try_into().map_err(|err| { + file.set_len(buf.len().try_into().map_err(|err: TryFromIntError| { RusticError::new( ErrorKind::Backend, "Failed to convert length to u64. This is a bug. Please report it.", ) - .add_context("length", buf.len()) + .add_context("length", buf.len().to_string()) .source(err.into()) })?) .map_err(|err| { diff --git a/crates/backend/src/util.rs b/crates/backend/src/util.rs index 78a98a4b..fa0e1d39 100644 --- a/crates/backend/src/util.rs +++ b/crates/backend/src/util.rs @@ -1,5 +1,5 @@ use crate::SupportedBackend; -use rustic_core::{BackendErrorKind, BackendResult, RusticError}; +use rustic_core::{ErrorKind, RusticError, RusticResult}; /// A backend location. This is a string that represents the location of the backend. #[derive(PartialEq, Eq, Debug)] @@ -58,18 +58,20 @@ pub fn location_to_type_and_path( BackendLocation(raw_location.to_string()), )), Some((scheme, path)) => Ok(( - SupportedBackend::try_from(scheme)?, + SupportedBackend::try_from(scheme).map_err(|err| { + RusticError::new( + ErrorKind::Parsing, + "The backend type is not supported. Please check the given backend and try again.", + ) + .add_context("name", scheme) + .source(err.into()) + })?, BackendLocation(path.to_string()), )), None => Ok(( SupportedBackend::Local, BackendLocation(raw_location.to_string()), )), - _ => Err(RusticError::new( - ErrorKind::Backend, - "The location is not convertible to a backend location. Please check the given location and try again.", - ) - .add_context("location", raw_location)), } } diff --git a/crates/core/src/commands/prune.rs b/crates/core/src/commands/prune.rs index c36908b6..0c5577fd 100644 --- a/crates/core/src/commands/prune.rs +++ b/crates/core/src/commands/prune.rs @@ -42,6 +42,7 @@ use crate::{ SnapshotFile, SnapshotId, }, repository::{Open, Repository}, + ErrorKind, }; pub(super) mod constants { @@ -676,10 +677,9 @@ impl PrunePlan { /// /// # Errors /// - /// * [`CommandErrorKind::RepackUncompressedRepoV1`] - If `repack_uncompressed` is set and the repository is a version 1 repository + /// * If `repack_uncompressed` is set and the repository is a version 1 repository /// * [`CommandErrorKind::FromOutOfRangeError`] - If `keep_pack` or `keep_delete` is out of range /// - /// [`CommandErrorKind::RepackUncompressedRepoV1`]: crate::error::CommandErrorKind::RepackUncompressedRepoV1 /// [`CommandErrorKind::FromOutOfRangeError`]: crate::error::CommandErrorKind::FromOutOfRangeError pub fn from_prune_options( repo: &Repository, @@ -688,9 +688,14 @@ impl PrunePlan { let pb = &repo.pb; let be = repo.dbe(); - if repo.config().version < 2 && opts.repack_uncompressed { - return Err(CommandErrorKind::RepackUncompressedRepoV1) - .map_err(|_err| todo!("Error transition")); + let version = repo.config().version; + + if version < 2 && opts.repack_uncompressed { + return Err(RusticError::new( + ErrorKind::Config, + "Repository is version 1, cannot repack uncompressed packs. ", + ) + .add_context("config version", version.to_string())); } let mut index_files = Vec::new(); @@ -781,10 +786,13 @@ impl PrunePlan { fn check(&self) -> RusticResult<()> { for (id, count) in &self.used_ids { if *count == 0 { - return Err(CommandErrorKind::BlobsMissing(*id)) - .map_err(|_err| todo!("Error transition")); + return Err( + RusticError::new(ErrorKind::Command, "Blob is missing in index files, this should not happen. Please report this issue.") + .add_context("blob id", id.to_string()), + ); } } + Ok(()) } @@ -1051,19 +1059,28 @@ impl PrunePlan { let check_size = || { match existing_size { Some(size) if size == pack.size => Ok(()), // size is ok => continue - Some(size) => Err(CommandErrorKind::PackSizeNotMatching( - pack.id, pack.size, size, - )) - .map_err(|_err| todo!("Error transition")), - None => Err(CommandErrorKind::PackNotExisting(pack.id)) - .map_err(|_err| todo!("Error transition")), + Some(size) => Err(RusticError::new( + ErrorKind::Command, + "Pack size does not match the size in the index file. This should not happen. Please report this issue.", + ) + .add_context("pack id", pack.id.to_string()) + .add_context("size in index (expected)", pack.size.to_string()) + .add_context("size in pack (real)", size.to_string()) + ), + None => Err(RusticError::new( + ErrorKind::Command, + "Pack does not exist. This should not happen. Please report this issue.", + ).add_context("pack id", pack.id.to_string())), } }; match pack.to_do { PackToDo::Undecided => { - return Err(CommandErrorKind::NoDecision(pack.id).into()) - .map_err(|_err| todo!("Error transition")) + return Err(RusticError::new( + ErrorKind::Command, + "Pack got no decision what to do with it, please report this!", + ) + .add_context("pack id", pack.id.to_string())); } PackToDo::Keep | PackToDo::Recover => { for blob in &pack.blobs { @@ -1194,7 +1211,10 @@ pub(crate) fn prune_repository( prune_plan: PrunePlan, ) -> RusticResult<()> { if repo.config().append_only == Some(true) { - return Err(CommandErrorKind::NotAllowedWithAppendOnly("prune".to_string()).into()); + return Err(RusticError::new( + ErrorKind::Repository, + "Repository is in append-only mode, pruning is not allowed. Aborting.", + )); } repo.warm_up_wait(prune_plan.repack_packs().into_iter())?; let be = repo.dbe(); @@ -1308,8 +1328,11 @@ pub(crate) fn prune_repository( .try_for_each(|pack| -> RusticResult<_> { match pack.to_do { PackToDo::Undecided => { - return Err(CommandErrorKind::NoDecision(pack.id)) - .map_err(|_err| todo!("Error transition")) + return Err(RusticError::new( + ErrorKind::Command, + "Pack got no decision what to do with it, please report this!", + ) + .add_context("pack id", pack.id.to_string())); } PackToDo::Keep => { // keep pack: add to new index diff --git a/crates/core/src/crypto/aespoly1305.rs b/crates/core/src/crypto/aespoly1305.rs index be2b68cb..76956d2c 100644 --- a/crates/core/src/crypto/aespoly1305.rs +++ b/crates/core/src/crypto/aespoly1305.rs @@ -98,7 +98,7 @@ impl CryptoKey for Key { ErrorKind::Cryptography, "Data decryption failed, MAC check failed.", ) - .add_context("nonce", format!("{:?}", nonce)) + .add_context("nonce", format!("{nonce:?}")) .source(err.into()) .code("C001".into()) }) @@ -124,7 +124,7 @@ impl CryptoKey for Key { .encrypt_in_place_detached(&nonce, &[], &mut res[16..]) .map_err(|err| { RusticError::new(ErrorKind::Cryptography, "Data encryption failed.") - .add_context("nonce", format!("{:?}", nonce)) + .add_context("nonce", format!("{nonce:?}")) .source(err.into()) })?; res.extend_from_slice(&tag); diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 640a5f60..c19b6524 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -84,7 +84,7 @@ impl Display for RusticError { } if let Some(severity) = &self.severity { - write!(f, "\n\nSeverity: {severity:?}", severity = severity)?; + write!(f, "\n\nSeverity: {severity:?}")?; } if let Some(status) = &self.status { @@ -111,7 +111,7 @@ impl Display for RusticError { )?; if let Some(backtrace) = &self.backtrace { - write!(f, "\n\nBacktrace:\n{:?}", backtrace)?; + write!(f, "\n\nBacktrace:\n{backtrace:?}")?; } Ok(()) @@ -120,6 +120,7 @@ impl Display for RusticError { // Accessors for anything we do want to expose publicly. impl RusticError { + /// Creates a new error with the given kind and guidance. pub fn new(kind: ErrorKind, guidance: impl Into) -> Self { Self { kind, @@ -138,6 +139,7 @@ impl RusticError { } } + /// Checks if the error has a specific error code. pub fn is_code(&self, code: &str) -> bool { self.code.as_ref().map_or(false, |c| c.as_str() == code) } @@ -154,6 +156,7 @@ impl RusticError { matches!(self.kind, ErrorKind::Password) } + /// Creates a new error from a given error. pub fn from( error: T, kind: ErrorKind, @@ -175,24 +178,40 @@ impl RusticError { } } + /// Adds a context to the error. + #[must_use] pub fn add_context(mut self, key: &'static str, value: impl Into) -> Self { self.context.push((key, value.into())); self } } +/// Severity of an error, ranging from informational to fatal. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Severity { + /// Informational Info, + + /// Warning Warning, + + /// Error Error, + + /// Fatal Fatal, } +/// Status of an error, indicating whether it is permanent, temporary, or persistent. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Status { + /// Permanent, may not be retried Permanent, + + /// Temporary, may be retried Temporary, + + /// Persistent, may be retried, but may not succeed Persistent, } @@ -341,15 +360,15 @@ pub mod immut_str { #[inline] pub fn as_str(&self) -> &str { match self { - ImmutStr::Static(s) => s, - ImmutStr::Owned(s) => s.as_ref(), + Self::Static(s) => s, + Self::Owned(s) => s.as_ref(), } } pub fn is_owned(&self) -> bool { match self { - ImmutStr::Static(_) => false, - ImmutStr::Owned(_) => true, + Self::Static(_) => false, + Self::Owned(_) => true, } } } @@ -362,13 +381,13 @@ pub mod immut_str { impl From<&'static str> for ImmutStr { fn from(s: &'static str) -> Self { - ImmutStr::Static(s) + Self::Static(s) } } impl From for ImmutStr { fn from(s: String) -> Self { - ImmutStr::Owned(s.into_boxed_str()) + Self::Owned(s.into_boxed_str()) } } diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 97591168..16a0cdb6 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -152,7 +152,8 @@ pub use crate::{ PathList, SnapshotGroup, SnapshotGroupCriterion, SnapshotOptions, StringList, }, repository::{ - CommandInput, FullIndex, IndexedFull, IndexedIds, IndexedStatus, IndexedTree, Open, - OpenStatus, Repository, RepositoryOptions, + command_input::{CommandInput, CommandInputErrorKind}, + FullIndex, IndexedFull, IndexedIds, IndexedStatus, IndexedTree, Open, OpenStatus, + Repository, RepositoryOptions, }, }; diff --git a/crates/core/src/repository.rs b/crates/core/src/repository.rs index 517b248c..24e4e4ad 100644 --- a/crates/core/src/repository.rs +++ b/crates/core/src/repository.rs @@ -1,8 +1,6 @@ pub(crate) mod command_input; pub(crate) mod warm_up; -pub use command_input::CommandInput; - use std::{ cmp::Ordering, fs::File, @@ -61,7 +59,10 @@ use crate::{ snapshotfile::{SnapshotGroup, SnapshotGroupCriterion, SnapshotId}, ConfigFile, KeyId, PathList, RepoFile, RepoId, SnapshotFile, SnapshotSummary, Tree, }, - repository::warm_up::{warm_up, warm_up_wait}, + repository::{ + command_input::CommandInput, + warm_up::{warm_up, warm_up_wait}, + }, vfs::OpenFile, RepositoryBackends, RusticError, }; diff --git a/crates/core/src/repository/command_input.rs b/crates/core/src/repository/command_input.rs index 2d98e548..7b5f2965 100644 --- a/crates/core/src/repository/command_input.rs +++ b/crates/core/src/repository/command_input.rs @@ -10,7 +10,7 @@ use serde_with::{serde_as, DisplayFromStr, PickFirst}; use crate::error::RusticResult; -/// [`CommandInputErrorKind`] describes the errors that can be returned from the CommandInput +/// [`CommandInputErrorKind`] describes the errors that can be returned from the `CommandInput` #[derive(thiserror::Error, Debug, displaydoc::Display)] #[non_exhaustive] pub enum CommandInputErrorKind { diff --git a/crates/testing/src/backend.rs b/crates/testing/src/backend.rs index 1c7ca4d1..51f6996a 100644 --- a/crates/testing/src/backend.rs +++ b/crates/testing/src/backend.rs @@ -2,11 +2,12 @@ pub mod in_memory_backend { use std::{collections::BTreeMap, sync::RwLock}; - use anyhow::{bail, Result}; use bytes::Bytes; use enum_map::EnumMap; - use rustic_core::{FileType, Id, ReadBackend, WriteBackend}; + use rustic_core::{ + ErrorKind, FileType, Id, ReadBackend, RusticError, RusticResult, WriteBackend, + }; #[derive(Debug)] /// In-Memory backend to be used for testing @@ -31,7 +32,7 @@ pub mod in_memory_backend { "test".to_string() } - fn list_with_size(&self, tpe: FileType) -> Result> { + fn list_with_size(&self, tpe: FileType) -> RusticResult> { Ok(self.0.read().unwrap()[tpe] .iter() .map(|(id, byte)| { @@ -43,7 +44,7 @@ pub mod in_memory_backend { .collect()) } - fn read_full(&self, tpe: FileType, id: &Id) -> Result { + fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult { Ok(self.0.read().unwrap()[tpe][id].clone()) } @@ -54,26 +55,35 @@ pub mod in_memory_backend { _cacheable: bool, offset: u32, length: u32, - ) -> Result { + ) -> RusticResult { Ok(self.0.read().unwrap()[tpe][id].slice(offset as usize..(offset + length) as usize)) } } impl WriteBackend for InMemoryBackend { - fn create(&self) -> Result<()> { + fn create(&self) -> RusticResult<()> { Ok(()) } - fn write_bytes(&self, tpe: FileType, id: &Id, _cacheable: bool, buf: Bytes) -> Result<()> { + fn write_bytes( + &self, + tpe: FileType, + id: &Id, + _cacheable: bool, + buf: Bytes, + ) -> RusticResult<()> { if self.0.write().unwrap()[tpe].insert(*id, buf).is_some() { - bail!("id {id} already exists"); + return Err(RusticError::new(ErrorKind::Backend, "Id already exists.") + .add_context("id", id.to_string())); } + Ok(()) } - fn remove(&self, tpe: FileType, id: &Id, _cacheable: bool) -> Result<()> { + fn remove(&self, tpe: FileType, id: &Id, _cacheable: bool) -> RusticResult<()> { if self.0.write().unwrap()[tpe].remove(id).is_none() { - bail!("id {id} doesn't exists"); + return Err(RusticError::new(ErrorKind::Backend, "Id does not exist.") + .add_context("id", id.to_string())); } Ok(()) } From b2c6914cc6658c8b26485432a199b96f30183d17 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 24 Oct 2024 06:03:33 +0200 Subject: [PATCH 025/129] RusticError for polynomial Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/chunker.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/core/src/chunker.rs b/crates/core/src/chunker.rs index 93d64965..af16bccc 100644 --- a/crates/core/src/chunker.rs +++ b/crates/core/src/chunker.rs @@ -8,6 +8,7 @@ use crate::{ rolling_hash::{Rabin64, RollingHash64}, }, error::RusticResult, + ErrorKind, RusticError, }; /// [`PolynomialErrorKind`] describes the errors that can happen while dealing with Polynomials @@ -181,9 +182,7 @@ impl Iterator for ChunkIter { /// /// # Errors /// -/// * [`PolynomialErrorKind::NoSuitablePolynomialFound`] - If no polynomial could be found in one million tries. -/// -/// [`PolynomialErrorKind::NoSuitablePolynomialFound`]: crate::error::PolynomialErrorKind::NoSuitablePolynomialFound +/// * If no polynomial could be found in one million tries. pub fn random_poly() -> RusticResult { for _ in 0..constants::RAND_POLY_MAX_TRIES { let mut poly: u64 = thread_rng().gen(); @@ -200,7 +199,10 @@ pub fn random_poly() -> RusticResult { } } - todo!("create rustic error Err(PolynomialErrorKind::NoSuitablePolynomialFound)"); + Err(RusticError::new( + ErrorKind::Polynomial, + "No suitable polynomial found, this should essentially never happen. Please try again, and then report this as a bug.", + )) } /// A trait for extending polynomials. From aa5cfc33baacb461ba7d91001a7805a1618de3e1 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 24 Oct 2024 06:30:09 +0200 Subject: [PATCH 026/129] use smolstr and box more Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- Cargo.lock | 20 +++++++ crates/core/Cargo.toml | 1 + crates/core/src/backend.rs | 2 +- crates/core/src/error.rs | 103 +++++-------------------------------- 4 files changed, 36 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f5d8519..c1781a42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -471,6 +471,15 @@ dependencies = [ "piper", ] +[[package]] +name = "borsh" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +dependencies = [ + "cfg_aliases", +] + [[package]] name = "bstr" version = "1.10.0" @@ -3366,6 +3375,7 @@ dependencies = [ "sha2", "shell-words", "simplelog", + "smol_str", "strum", "tar", "tempfile", @@ -3800,6 +3810,16 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "smol_str" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9676b89cd56310a87b93dec47b11af744f34d5fc9f367b829474eec0a891350d" +dependencies = [ + "borsh", + "serde", +] + [[package]] name = "socket2" version = "0.5.7" diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 98739b4e..94109f6d 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -108,6 +108,7 @@ quick_cache = "0.6.9" shell-words = "1.1.0" strum = { version = "0.26.3", features = ["derive"] } zstd = "0.13.2" +smol_str = "0.3.2" [target.'cfg(not(windows))'.dependencies] sha2 = { version = "0.10.8", features = ["asm"] } diff --git a/crates/core/src/backend.rs b/crates/core/src/backend.rs index 1b2ddaff..9ca12b95 100644 --- a/crates/core/src/backend.rs +++ b/crates/core/src/backend.rs @@ -75,7 +75,7 @@ pub enum CryptBackendErrorKind { WritingDataInCryptBackendFailed, /// failed to list Ids ListingIdsInDecryptionBackendFailed, - /// writing full hash failed in CryptBackend + /// writing full hash failed in `CryptBackend` WritingFullHashFailed, /// decoding Zstd compressed data failed: `{0:?}` DecodingZstdCompressedDataFailed(std::io::Error), diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index c19b6524..bd150a65 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -6,13 +6,12 @@ // use std::fmt; use derive_setters::Setters; +use smol_str::SmolStr; use std::{ backtrace::Backtrace, fmt::{self, Display}, }; -use crate::error::immut_str::ImmutStr; - pub(crate) mod constants { pub const DEFAULT_DOCS_URL: &str = "https://rustic.cli.rs/docs/errors/"; pub const DEFAULT_ISSUE_URL: &str = "https://github.com/rustic-rs/rustic_core/issues/new"; @@ -33,22 +32,22 @@ pub struct RusticError { source: Option>, /// The error message with guidance. - guidance: ImmutStr, + guidance: SmolStr, /// The context of the error. - context: Vec<(&'static str, String)>, + context: Box<[(&'static str, SmolStr)]>, /// The URL of the documentation for the error. - docs_url: Option, + docs_url: Option, /// Error code. - code: Option, + code: Option, /// The URL of the issue tracker for opening a new issue. - new_issue_url: Option, + new_issue_url: Option, /// The URL of an already existing issue that is related to this error. - existing_issue_url: Option, + existing_issue_url: Option, /// Severity of the error. severity: Option, @@ -92,7 +91,7 @@ impl Display for RusticError { } if let Some(code) = &self.code { - let default_docs_url = ImmutStr::from(constants::DEFAULT_DOCS_URL); + let default_docs_url = SmolStr::from(constants::DEFAULT_DOCS_URL); let docs_url = self.docs_url.as_ref().unwrap_or(&default_docs_url); write!(f, "\n\nFor more information, see: {docs_url}/{code}")?; @@ -102,7 +101,7 @@ impl Display for RusticError { write!(f, "\n\nThis might be a related issue, please check it for a possible workaround and/or further guidance: {existing_issue_url}")?; } - let default_issue_url = ImmutStr::from(constants::DEFAULT_ISSUE_URL); + let default_issue_url = SmolStr::from(constants::DEFAULT_ISSUE_URL); let new_issue_url = self.new_issue_url.as_ref().unwrap_or(&default_issue_url); write!( @@ -125,7 +124,7 @@ impl RusticError { Self { kind, guidance: guidance.into().into(), - context: Vec::default(), + context: Box::default(), source: None, code: None, docs_url: None, @@ -164,7 +163,7 @@ impl RusticError { Self { kind, guidance: error.to_string().into(), - context: Vec::default(), + context: Box::default(), source: Some(Box::new(error)), code: None, docs_url: None, @@ -181,7 +180,9 @@ impl RusticError { /// Adds a context to the error. #[must_use] pub fn add_context(mut self, key: &'static str, value: impl Into) -> Self { - self.context.push((key, value.into())); + let mut context = self.context.to_vec(); + context.push((key, value.into().into())); + self.context = context.into_boxed_slice(); self } } @@ -329,82 +330,6 @@ pub enum ErrorKind { // - **Rclone Errors**: e.g., `NoOutputForRcloneVersion`, `NoStdOutForRclone`, `RCloneExitWithBadStatus` // - **REST API Errors**: e.g., `NotSupportedForRetry`, `UrlParsingFailed` -pub mod immut_str { - //! Copyright 2024 Cloudflare, Inc. - //! - //! Licensed under the Apache License, Version 2.0 (the "License"); - //! you may not use this file except in compliance with the License. - //! You may obtain a copy of the License at - //! - //! http://www.apache.org/licenses/LICENSE-2.0 - //! - //! Unless required by applicable law or agreed to in writing, software - //! distributed under the License is distributed on an "AS IS" BASIS, - //! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - //! See the License for the specific language governing permissions and - //! limitations under the License. - //! - //! Taken from - - use std::fmt; - - /// A data struct that holds either immutable string or reference to static str. - /// Compared to String or `Box`, it avoids memory allocation on static str. - #[derive(Debug, PartialEq, Eq, Clone)] - pub enum ImmutStr { - Static(&'static str), - Owned(Box), - } - - impl ImmutStr { - #[inline] - pub fn as_str(&self) -> &str { - match self { - Self::Static(s) => s, - Self::Owned(s) => s.as_ref(), - } - } - - pub fn is_owned(&self) -> bool { - match self { - Self::Static(_) => false, - Self::Owned(_) => true, - } - } - } - - impl fmt::Display for ImmutStr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.as_str()) - } - } - - impl From<&'static str> for ImmutStr { - fn from(s: &'static str) -> Self { - Self::Static(s) - } - } - - impl From for ImmutStr { - fn from(s: String) -> Self { - Self::Owned(s.into_boxed_str()) - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[test] - fn test_static_vs_owned() { - let s: ImmutStr = "test".into(); - assert!(!s.is_owned()); - let s: ImmutStr = "test".to_string().into(); - assert!(s.is_owned()); - } - } -} - #[cfg(test)] mod tests { use std::sync::LazyLock; From 246a1c754e1902f0b1837f07ece572504478b7a9 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 24 Oct 2024 06:40:10 +0200 Subject: [PATCH 027/129] more RusticErrors Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/archiver.rs | 12 ++++++++---- crates/core/src/blob.rs | 15 ++++++++++++++- crates/core/src/error.rs | 2 ++ crates/core/src/index.rs | 9 ++++++++- 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/crates/core/src/archiver.rs b/crates/core/src/archiver.rs index 4dedeb47..a959b4f0 100644 --- a/crates/core/src/archiver.rs +++ b/crates/core/src/archiver.rs @@ -22,7 +22,7 @@ use crate::{ ReadGlobalIndex, }, repofile::{configfile::ConfigFile, snapshotfile::SnapshotFile}, - Progress, + ErrorKind, Progress, RusticError, }; /// [`ArchiverErrorKind`] describes the errors that can be returned from the archiver @@ -241,9 +241,13 @@ impl<'a, BE: DecryptFullBackend, I: ReadGlobalIndex> Archiver<'a, BE, I> { self.indexer.write().unwrap().finalize()?; - summary - .finalize(self.snap.time) - .map_err(|_err| todo!("Error transition"))?; + summary.finalize(self.snap.time).map_err(|err| { + RusticError::new( + ErrorKind::Processing, + "Could not finalize summary, please check the logs for more information.", + ) + .source(err.into()) + })?; self.snap.summary = Some(summary); if !skip_identical_parent || Some(self.snap.tree) != self.parent.tree_id() { diff --git a/crates/core/src/blob.rs b/crates/core/src/blob.rs index 1ae3de33..a5068c56 100644 --- a/crates/core/src/blob.rs +++ b/crates/core/src/blob.rs @@ -11,14 +11,27 @@ use crate::define_new_id_struct; pub const ALL_BLOB_TYPES: [BlobType; 2] = [BlobType::Tree, BlobType::Data]; #[derive( - Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Enum, + Serialize, + Deserialize, + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Enum, + strum::Display, )] /// The type a `blob` or a `packfile` can have pub enum BlobType { #[serde(rename = "tree")] + #[strum(to_string = "tree")] /// This is a tree blob Tree, #[serde(rename = "data")] + #[strum(to_string = "data")] /// This is a data blob Data, } diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index bd150a65..0dd03996 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -256,6 +256,8 @@ pub enum ErrorKind { Polynomial, /// Multithreading Error Multithreading, + /// Processing Error + Processing, // /// The repository password is incorrect. Please try again. // IncorrectRepositoryPassword, // /// No repository given. Please use the --repository option. diff --git a/crates/core/src/index.rs b/crates/core/src/index.rs index 3ab84bfd..0156190b 100644 --- a/crates/core/src/index.rs +++ b/crates/core/src/index.rs @@ -13,6 +13,7 @@ use crate::{ indexfile::{IndexBlob, IndexFile}, packfile::PackId, }, + ErrorKind, RusticError, }; pub(crate) mod binarysorted; @@ -202,7 +203,13 @@ pub trait ReadIndex { id: &BlobId, ) -> RusticResult { self.get_id(tpe, id).map_or_else( - || Err(IndexErrorKind::BlobInIndexNotFound).map_err(|_err| todo!("Error transition")), + || { + Err( + RusticError::new(ErrorKind::Index, "Blob not found in index") + .add_context("blob id", id.to_string()) + .add_context("blob type", tpe.to_string()), + ) + }, |ie| ie.read_data(be), ) } From ed19682787aaf6d6d7153db18c9bd48262fb80d6 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 24 Oct 2024 18:11:55 +0200 Subject: [PATCH 028/129] style: fmt Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 734fec5f..7f0f337e 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -106,9 +106,9 @@ humantime = "2.1.0" itertools = "0.13.0" quick_cache = "0.6.9" shell-words = "1.1.0" +smol_str = "0.3.2" strum = { version = "0.26.3", features = ["derive"] } zstd = "0.13.2" -smol_str = "0.3.2" [target.'cfg(not(windows))'.dependencies] sha2 = { version = "0.10.8", features = ["asm"] } From f86f4b53e373788434544de1195bd38cd6afc470 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 24 Oct 2024 20:54:06 +0200 Subject: [PATCH 029/129] RusticErrors for opendal backend Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/opendal.rs | 132 ++++++++++++++++++++++++---------- 1 file changed, 93 insertions(+), 39 deletions(-) diff --git a/crates/backend/src/opendal.rs b/crates/backend/src/opendal.rs index b5828ff6..ce433e9d 100644 --- a/crates/backend/src/opendal.rs +++ b/crates/backend/src/opendal.rs @@ -1,18 +1,20 @@ /// `OpenDAL` backend for rustic. -use std::{collections::HashMap, str::FromStr, sync::OnceLock}; +use std::{collections::HashMap, num::TryFromIntError, str::FromStr, sync::OnceLock}; use bytes::Bytes; use bytesize::ByteSize; use log::trace; use opendal::{ layers::{BlockingLayer, ConcurrentLimitLayer, LoggingLayer, RetryLayer, ThrottleLayer}, - BlockingOperator, ErrorKind, Metakey, Operator, Scheme, + BlockingOperator, Metakey, Operator, Scheme, }; use rayon::prelude::{IntoParallelIterator, ParallelIterator}; use tokio::runtime::Runtime; use typed_path::UnixPathBuf; -use rustic_core::{FileType, Id, ReadBackend, RusticResult, WriteBackend, ALL_FILE_TYPES}; +use rustic_core::{ + ErrorKind, FileType, Id, ReadBackend, RusticError, RusticResult, WriteBackend, ALL_FILE_TYPES, +}; mod constants { /// Default number of retries @@ -46,19 +48,35 @@ pub struct Throttle { impl FromStr for Throttle { type Err = RusticError; - fn from_str(s: &str) -> RusticResult { + fn from_str(s: &str) -> Result { let mut values = s .split(',') .map(|s| { - ByteSize::from_str(s.trim()).map_err(|err| Err(format!("Error: {err}")).into()) + ByteSize::from_str(s.trim()).map_err(|err| { + RusticError::new( + ErrorKind::Parsing, + "Parsing ByteSize from throttle string failed", + ) + .add_context("string", s) + .source(err.into()) + }) }) - .map(|b| -> RusticResult { Ok(b?.as_u64().try_into()?) }); + .map(|b| -> RusticResult { + Ok(b?.as_u64().try_into().map_err(|err: TryFromIntError| { + RusticError::new(ErrorKind::Parsing, "Converting ByteSize to u32 failed") + .source(err.into()) + })?) + }); let bandwidth = values .next() - .ok_or_else(|| Err("no bandwidth given".to_string()).into())??; + .transpose()? + .ok_or_else(|| RusticError::new(ErrorKind::Parsing, "No bandwidth given."))?; + let burst = values .next() - .ok_or_else(|| Err("no burst given".to_string()).into())??; + .transpose()? + .ok_or_else(|| RusticError::new(ErrorKind::Parsing, "No burst given."))?; + Ok(Self { bandwidth, burst }) } } @@ -82,20 +100,22 @@ impl OpenDALBackend { let max_retries = match options.get("retry").map(String::as_str) { Some("false" | "off") => 0, None | Some("default") => constants::DEFAULT_RETRY, - Some(value) => usize::from_str(value)?, + Some(value) => usize::from_str(value).map_err(|_err| todo!("Error transition"))?, }; let connections = options .get("connections") .map(|c| usize::from_str(c)) - .transpose()?; + .transpose() + .map_err(|_err| todo!("Error transition"))?; let throttle = options .get("throttle") .map(|t| Throttle::from_str(t)) .transpose()?; - let schema = Scheme::from_str(path.as_ref())?; - let mut operator = Operator::via_iter(schema, options)? + let schema = Scheme::from_str(path.as_ref()).map_err(|_err| todo!("Error transition"))?; + let mut operator = Operator::via_iter(schema, options) + .map_err(|_err| todo!("Error transition"))? .layer(RetryLayer::new().with_max_times(max_retries).with_jitter()); if let Some(Throttle { bandwidth, burst }) = throttle { @@ -109,7 +129,7 @@ impl OpenDALBackend { let _guard = runtime().enter(); let operator = operator .layer(LoggingLayer::default()) - .layer(BlockingLayer::create()?) + .layer(BlockingLayer::create().map_err(|_err| todo!("Error transition"))?) .blocking(); Ok(Self { operator }) @@ -162,18 +182,25 @@ impl ReadBackend for OpenDALBackend { fn list(&self, tpe: FileType) -> RusticResult> { trace!("listing tpe: {tpe:?}"); if tpe == FileType::Config { - return Ok(if self.operator.is_exist("config")? { - vec![Id::default()] - } else { - Vec::new() - }); + return Ok( + if self + .operator + .is_exist("config") + .map_err(|_err| todo!("Error transition"))? + { + vec![Id::default()] + } else { + Vec::new() + }, + ); } Ok(self .operator .list_with(&(tpe.dirname().to_string() + "/")) .recursive(true) - .call()? + .call() + .map_err(|_err| todo!("Error transition"))? .into_iter() .filter(|e| e.metadata().is_file()) .filter_map(|e| e.name().parse().ok()) @@ -190,9 +217,15 @@ impl ReadBackend for OpenDALBackend { trace!("listing tpe: {tpe:?}"); if tpe == FileType::Config { return match self.operator.stat("config") { - Ok(entry) => Ok(vec![(Id::default(), entry.content_length().try_into()?)]), - Err(err) if err.kind() == ErrorKind::NotFound => Ok(Vec::new()), - Err(err) => Err(err.into()), + Ok(entry) => Ok(vec![( + Id::default(), + entry + .content_length() + .try_into() + .map_err(|_err| todo!("Error transition"))?, + )]), + Err(err) if err.kind() == opendal::ErrorKind::NotFound => Ok(Vec::new()), + Err(err) => Err(err).map_err(|_err| todo!("Error transition")), }; } @@ -201,11 +234,18 @@ impl ReadBackend for OpenDALBackend { .list_with(&(tpe.dirname().to_string() + "/")) .recursive(true) .metakey(Metakey::ContentLength) - .call()? + .call() + .map_err(|_err| todo!("Error transition"))? .into_iter() .filter(|e| e.metadata().is_file()) .map(|e| -> RusticResult<(Id, u32)> { - Ok((e.name().parse()?, e.metadata().content_length().try_into()?)) + Ok(( + e.name().parse()?, + e.metadata() + .content_length() + .try_into() + .map_err(|_err| todo!("Error transition"))?, + )) }) .filter_map(RusticResult::ok) .collect()) @@ -214,7 +254,11 @@ impl ReadBackend for OpenDALBackend { fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult { trace!("reading tpe: {tpe:?}, id: {id}"); - Ok(self.operator.read(&self.path(tpe, id))?.to_bytes()) + Ok(self + .operator + .read(&self.path(tpe, id)) + .map_err(|_err| todo!("Error transition"))? + .to_bytes()) } fn read_partial( @@ -231,7 +275,8 @@ impl ReadBackend for OpenDALBackend { .operator .read_with(&self.path(tpe, id)) .range(range) - .call()? + .call() + .map_err(|_err| todo!("Error transition"))? .to_bytes()) } } @@ -243,18 +288,22 @@ impl WriteBackend for OpenDALBackend { for tpe in ALL_FILE_TYPES { self.operator - .create_dir(&(tpe.dirname().to_string() + "/"))?; + .create_dir(&(tpe.dirname().to_string() + "/")) + .map_err(|_err| todo!("Error transition"))?; } // creating 256 dirs can be slow on remote backends, hence we parallelize it. - (0u8..=255).into_par_iter().try_for_each(|i| { - self.operator.create_dir( - &(UnixPathBuf::from("data") - .join(hex::encode([i])) - .to_string_lossy() - .to_string() - + "/"), - ) - })?; + (0u8..=255) + .into_par_iter() + .try_for_each(|i| { + self.operator.create_dir( + &(UnixPathBuf::from("data") + .join(hex::encode([i])) + .to_string_lossy() + .to_string() + + "/"), + ) + }) + .map_err(|_err| todo!("Error transition"))?; Ok(()) } @@ -276,7 +325,9 @@ impl WriteBackend for OpenDALBackend { ) -> RusticResult<()> { trace!("writing tpe: {:?}, id: {}", &tpe, &id); let filename = self.path(tpe, id); - self.operator.write(&filename, buf)?; + self.operator + .write(&filename, buf) + .map_err(|_err| todo!("Error transition"))?; Ok(()) } @@ -290,7 +341,9 @@ impl WriteBackend for OpenDALBackend { fn remove(&self, tpe: FileType, id: &Id, _cacheable: bool) -> RusticResult<()> { trace!("removing tpe: {:?}, id: {}", &tpe, &id); let filename = self.path(tpe, id); - self.operator.delete(&filename)?; + self.operator + .delete(&filename) + .map_err(|_err| todo!("Error transition"))?; Ok(()) } } @@ -298,6 +351,7 @@ impl WriteBackend for OpenDALBackend { #[cfg(test)] mod tests { use super::*; + use anyhow::Result; use rstest::rstest; use serde::Deserialize; use std::{fs, path::PathBuf}; @@ -324,7 +378,7 @@ mod tests { #[rstest] fn new_opendal_backend( #[files("tests/fixtures/opendal/*.toml")] test_case: PathBuf, - ) -> RusticResult<()> { + ) -> Result<()> { #[derive(Deserialize)] struct TestCase { path: String, From 8ff3003000acfddb63923bb4c2ed227a5df7b67d Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 24 Oct 2024 21:25:13 +0200 Subject: [PATCH 030/129] RusticErrors for rclone backend Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/rclone.rs | 47 +++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/crates/backend/src/rclone.rs b/crates/backend/src/rclone.rs index 8d66e434..697031c1 100644 --- a/crates/backend/src/rclone.rs +++ b/crates/backend/src/rclone.rs @@ -16,7 +16,9 @@ use semver::{BuildMetadata, Prerelease, Version, VersionReq}; use crate::rest::RestBackend; -use rustic_core::{CommandInput, FileType, Id, ReadBackend, RusticResult, WriteBackend}; +use rustic_core::{ + CommandInput, ErrorKind, FileType, Id, ReadBackend, RusticError, RusticResult, WriteBackend, +}; /// [`RcloneErrorKind`] describes the errors that can be returned by a backend provider #[derive(thiserror::Error, Debug, displaydoc::Display)] @@ -33,9 +35,6 @@ pub enum RcloneErrorKind { /// StdIo Error: `{0:?}` #[error(transparent)] FromIoError(std::io::Error), - /// utf8 error: `{0:?}` - #[error(transparent)] - FromUtf8Error(Utf8Error), /// error parsing version number from `{0:?}` FromParseVersion(String), /// Using rclone without authentication! Upgrade to rclone >= 1.52.2 (current version: `{0}`)! @@ -95,13 +94,28 @@ impl Drop for RcloneBackend { /// [`RcloneErrorKind::FromParseVersion`]: RcloneErrorKind::FromParseVersion fn check_clone_version(rclone_version_output: &[u8]) -> RusticResult<()> { let rclone_version = std::str::from_utf8(rclone_version_output) - .map_err(RcloneErrorKind::FromUtf8Error)? + .map_err(|err| { + RusticError::new( + ErrorKind::Parsing, + "Expected rclone version to be valid utf8, but it was not.", + ) + .source(err.into()) + })? .lines() .next() - .ok_or_else(|| RcloneErrorKind::NoOutputForRcloneVersion)? + .ok_or_else(|| { + RusticError::new( + ErrorKind::Parsing, + "Expected rclone version to have at least one line, but it did not. Please check the rclone version output manually.", + ) + })? .trim_start_matches(|c: char| !c.is_numeric()); - let mut parsed_version = Version::parse(rclone_version)?; + let mut parsed_version = Version::parse(rclone_version).map_err(|err| { + RusticError::new(ErrorKind::Parsing, "Error parsing rclone version.") + .add_context("version", rclone_version) + .source(err.into()) + })?; // we need to set the pre and build fields to empty to make the comparison work // otherwise the comparison will take the pre and build fields into account @@ -114,10 +128,21 @@ fn check_clone_version(rclone_version_output: &[u8]) -> RusticResult<()> { // we hard fail here to prevent this, as we can't guarantee the security of the data // also because 1.52.2 has been released on Jun 24, 2020, we can assume that this is a // reasonable lower bound for the version - if VersionReq::parse("<1.52.2")?.matches(&parsed_version) { - return Err( - RcloneErrorKind::RCloneWithoutAuthentication(rclone_version.to_string()).into(), - ); + if VersionReq::parse("<1.52.2") + .map_err(|err| { + RusticError::new( + ErrorKind::Parsing, + "Error parsing version requirement. This should not happen.", + ) + .source(err.into()) + })? + .matches(&parsed_version) + { + return Err(RusticError::new( + ErrorKind::Unsupported, + "Unsupported rclone version. Using rclone without authentication! Upgrade to rclone >= 1.52.2!", + ) + .add_context("current version", rclone_version.to_string())); } Ok(()) From 1856987958d6b718bca4633a9f8c848690ebfc47 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Fri, 25 Oct 2024 18:06:47 +0200 Subject: [PATCH 031/129] RusticErrors for Rclone backend Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/rclone.rs | 93 +++++++++++++++++++++++------------- crates/core/src/error.rs | 4 ++ 2 files changed, 64 insertions(+), 33 deletions(-) diff --git a/crates/backend/src/rclone.rs b/crates/backend/src/rclone.rs index 697031c1..e70d281e 100644 --- a/crates/backend/src/rclone.rs +++ b/crates/backend/src/rclone.rs @@ -2,6 +2,7 @@ use std::{ collections::HashMap, io::{BufRead, BufReader}, process::{Child, Command, Stdio}, + str::ParseBoolError, thread::JoinHandle, }; @@ -17,30 +18,10 @@ use semver::{BuildMetadata, Prerelease, Version, VersionReq}; use crate::rest::RestBackend; use rustic_core::{ - CommandInput, ErrorKind, FileType, Id, ReadBackend, RusticError, RusticResult, WriteBackend, + CommandInput, CommandInputErrorKind, ErrorKind, FileType, Id, ReadBackend, RusticError, + RusticResult, WriteBackend, }; -/// [`RcloneErrorKind`] describes the errors that can be returned by a backend provider -#[derive(thiserror::Error, Debug, displaydoc::Display)] -#[non_exhaustive] -pub enum RcloneErrorKind { - /// 'rclone version' doesn't give any output - NoOutputForRcloneVersion, - /// cannot get stdout of rclone - NoStdOutForRclone, - /// rclone exited with `{0:?}` - RCloneExitWithBadStatus(ExitStatus), - /// url must start with http:\/\/! url: {0:?} - UrlNotStartingWithHttp(String), - /// StdIo Error: `{0:?}` - #[error(transparent)] - FromIoError(std::io::Error), - /// error parsing version number from `{0:?}` - FromParseVersion(String), - /// Using rclone without authentication! Upgrade to rclone >= 1.52.2 (current version: `{0}`)! - RCloneWithoutAuthentication(String), -} - pub(super) mod constants { /// The default command called if no other is specified pub(super) const DEFAULT_COMMAND: &str = "rclone serve restic --addr localhost:0"; @@ -140,7 +121,7 @@ fn check_clone_version(rclone_version_output: &[u8]) -> RusticResult<()> { { return Err(RusticError::new( ErrorKind::Unsupported, - "Unsupported rclone version. Using rclone without authentication! Upgrade to rclone >= 1.52.2!", + "Unsupported rclone version. We must not use rclone without authentication! Please upgrade to rclone >= 1.52.2!", ) .add_context("current version", rclone_version.to_string())); } @@ -179,7 +160,13 @@ impl RcloneBackend { let rclone_command = options.get("rclone-command"); let use_password = options .get("use-password") - .map(|v| v.parse()) + .map(|v| v.parse().map_err(|err: ParseBoolError| + RusticError::new( + ErrorKind::Parsing, + "Expected 'use-password' to be a boolean, but it was not. Please check the configuration file.", + ) + .source(err.into()) + )) .transpose()? .unwrap_or(true); @@ -187,7 +174,10 @@ impl RcloneBackend { let rclone_version_output = Command::new("rclone") .arg("version") .output() - .map_err(RcloneErrorKind::FromIoError)? + .map_err(|err| RusticError::new( + ErrorKind::ExternalCommand, + "Experienced an error while running `rclone version` command. Please check if rclone is installed and in your PATH.", + ).source(err.into()))? .stdout; // if we want to use a password and rclone_command is not explicitly set, @@ -202,13 +192,18 @@ impl RcloneBackend { let mut rclone_command = rclone_command.map_or(DEFAULT_COMMAND.to_string(), Clone::clone); rclone_command.push(' '); rclone_command.push_str(url.as_ref()); - let rclone_command: CommandInput = rclone_command.parse()?; + let rclone_command: CommandInput = rclone_command.parse().map_err( + |err: CommandInputErrorKind| RusticError::new( + ErrorKind::Parsing, + "Expected rclone command to be valid, but it was not. Please check the configuration file.", + ) + .source(err.into()) + )?; debug!("starting rclone via {rclone_command:?}"); let mut command = Command::new(rclone_command.command()); if use_password { - // TODO: We should handle errors here _ = command .env("RCLONE_USER", &user) .env("RCLONE_PASS", &password); @@ -218,25 +213,53 @@ impl RcloneBackend { .args(rclone_command.args()) .stderr(Stdio::piped()) .spawn() - .map_err(RcloneErrorKind::FromIoError)?; + .map_err(|err| + RusticError::new( + ErrorKind::ExternalCommand, + "Experienced an error while running rclone. Please check if rclone is installed and working correctly.", + ) + .add_context("rclone command", rclone_command.to_string()) + .source(err.into()))?; let mut stderr = BufReader::new( child .stderr .take() - .ok_or_else(|| RcloneErrorKind::NoStdOutForRclone)?, + .ok_or_else(|| RusticError::new( + ErrorKind::ExternalCommand, + "Could not get stderr of rclone. Please check if rclone is installed and working correctly.", + ))?, ); let mut rest_url = match options.get("rest-url") { None => { loop { - if let Some(status) = child.try_wait().map_err(RcloneErrorKind::FromIoError)? { - return Err(RcloneErrorKind::RCloneExitWithBadStatus(status).into()); + if let Some(status) = child.try_wait().map_err(|err| + RusticError::new( + ErrorKind::ExternalCommand, + "Experienced an error while running rclone. Please check if rclone is installed and working correctly.", + ) + .source(err.into()) + )? { + return Err( + RusticError::new( + ErrorKind::ExternalCommand, + "rclone exited before it could start the REST server. Please check the exit status for more information.", + ).add_context("exit status", status.to_string()) + ); } let mut line = String::new(); + _ = stderr .read_line(&mut line) - .map_err(RcloneErrorKind::FromIoError)?; + .map_err(|err| + RusticError::new( + ErrorKind::Io, + "Experienced an error while reading rclone output. Please check if rclone is installed and working correctly.", + ) + .source(err.into()) + )?; + match line.find(constants::SEARCHSTRING) { Some(result) => { if let Some(url) = line.get(result + constants::SEARCHSTRING.len()..) { @@ -255,7 +278,11 @@ impl RcloneBackend { if use_password { if !rest_url.starts_with("http://") { - return Err(RcloneErrorKind::UrlNotStartingWithHttp(rest_url).into()); + return Err(RusticError::new( + ErrorKind::Io, + "Please make sure, the URL starts with 'http://'!", + ) + .add_context("url", rest_url)); } rest_url = format!("http://{user}:{password}@{}", &rest_url[7..]); } diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 0dd03996..b6d96991 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -258,6 +258,10 @@ pub enum ErrorKind { Multithreading, /// Processing Error Processing, + /// Something is not supported + Unsupported, + /// External Command + ExternalCommand, // /// The repository password is incorrect. Please try again. // IncorrectRepositoryPassword, // /// No repository given. Please use the --repository option. From 3a045ef5b58503baf047812d71fb99b0783be7bc Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Fri, 25 Oct 2024 18:41:13 +0200 Subject: [PATCH 032/129] use derive_more display instead of strum for BlobType Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/blob.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/core/src/blob.rs b/crates/core/src/blob.rs index a5068c56..5b0a4c5e 100644 --- a/crates/core/src/blob.rs +++ b/crates/core/src/blob.rs @@ -22,16 +22,14 @@ pub const ALL_BLOB_TYPES: [BlobType; 2] = [BlobType::Tree, BlobType::Data]; Ord, Hash, Enum, - strum::Display, + derive_more::Display, )] /// The type a `blob` or a `packfile` can have pub enum BlobType { #[serde(rename = "tree")] - #[strum(to_string = "tree")] /// This is a tree blob Tree, #[serde(rename = "data")] - #[strum(to_string = "data")] /// This is a data blob Data, } From 8ffaa70c836451088f7c86ad54c85c7c82bf858a Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Fri, 25 Oct 2024 19:18:16 +0200 Subject: [PATCH 033/129] RusticErrors for Rest backend Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/rest.rs | 226 ++++++++++++++++++++++++------------- 1 file changed, 145 insertions(+), 81 deletions(-) diff --git a/crates/backend/src/rest.rs b/crates/backend/src/rest.rs index 46bbb039..c4a8f391 100644 --- a/crates/backend/src/rest.rs +++ b/crates/backend/src/rest.rs @@ -13,34 +13,31 @@ use reqwest::{ use serde::Deserialize; use thiserror::Error; -use rustic_core::{FileType, Id, ReadBackend, WriteBackend}; +use rustic_core::{ErrorKind, FileType, Id, ReadBackend, RusticError, RusticResult, WriteBackend}; /// [`RestErrorKind`] describes the errors that can be returned while dealing with the REST API #[derive(Error, Debug, Display)] #[non_exhaustive] pub enum RestErrorKind { - /// value `{0:?}` not supported for option retry! - NotSupportedForRetry(String), - /// parsing failed for url: `{0:?}` - UrlParsingFailed(url::ParseError), #[cfg(feature = "rest")] /// requesting resource failed: `{0:?}` RequestingResourceFailed(reqwest::Error), - /// couldn't parse duration in humantime library: `{0:?}` - CouldNotParseDuration(humantime::DurationError), #[cfg(feature = "rest")] /// backoff failed: {0:?} BackoffError(backoff::Error), - #[cfg(feature = "rest")] - /// Failed to build HTTP client: `{0:?}` - BuildingClientFailed(reqwest::Error), - /// joining URL failed on: {0:?} + /// joining URL failed on: {0} JoiningUrlFailed(url::ParseError), } -mod consts { +pub(super) mod constants { + use std::time::Duration; + /// Default number of retries pub(super) const DEFAULT_RETRY: usize = 5; + + /// Default timeout for the client + /// This is set to 10 minutes + pub(super) const DEFAULT_TIMEOUT: Duration = Duration::from_secs(600); } // trait CheckError to add user-defined method check_error on Response @@ -88,7 +85,7 @@ struct LimitRetryBackoff { impl Default for LimitRetryBackoff { fn default() -> Self { Self { - max_retries: consts::DEFAULT_RETRY, + max_retries: constants::DEFAULT_RETRY, retries: 0, exp: ExponentialBackoffBuilder::new() .with_max_elapsed_time(None) // no maximum elapsed time; we count number of retires @@ -152,53 +149,77 @@ impl RestBackend { /// /// # Errors /// - /// * [`RestErrorKind::UrlParsingFailed`] - If the url could not be parsed. - /// * [`RestErrorKind::BuildingClientFailed`] - If the client could not be built. - /// - /// [`RestErrorKind::UrlParsingFailed`]: RestErrorKind::UrlParsingFailed - /// [`RestErrorKind::BuildingClientFailed`]: RestErrorKind::BuildingClientFailed + /// * If the url could not be parsed. + /// * If the client could not be built. pub fn new( url: impl AsRef, options: impl IntoIterator, ) -> RusticResult { - let url = url.as_ref(); + let url = url.as_ref().to_string(); + let url = if url.ends_with('/') { - Url::parse(url).map_err(RestErrorKind::UrlParsingFailed)? + url } else { // add a trailing '/' if there is none - let mut url = url.to_string(); + let mut url = url; url.push('/'); - Url::parse(&url).map_err(RestErrorKind::UrlParsingFailed)? + url }; + let url = Url::parse(&url).map_err(|err| { + RusticError::new(ErrorKind::Parsing, "URL parsing failed") + .add_context("url", url) + .source(err.into()) + })?; + let mut headers = HeaderMap::new(); _ = headers.insert("User-Agent", HeaderValue::from_static("rustic")); let mut client = ClientBuilder::new() .default_headers(headers) - .timeout(Duration::from_secs(600)) // set default timeout to 10 minutes (we can have *large* packfiles) + .timeout(constants::DEFAULT_TIMEOUT) // set default timeout to 10 minutes (we can have *large* packfiles) .build() - .map_err(RestErrorKind::BuildingClientFailed)?; + .map_err(|err| { + RusticError::new(ErrorKind::Backend, "Failed to build HTTP client") + .source(err.into()) + })?; let mut backoff = LimitRetryBackoff::default(); + // FIXME: If we have multiple times the same option, this could lead to unexpected behavior for (option, value) in options { if option == "retry" { let max_retries = match value.as_str() { "false" | "off" => 0, - "default" => consts::DEFAULT_RETRY, - _ => usize::from_str(&value) - .map_err(|_| RestErrorKind::NotSupportedForRetry(value))?, + "default" => constants::DEFAULT_RETRY, + _ => usize::from_str(&value).map_err(|err| { + RusticError::new( + ErrorKind::Parsing, + "Cannot parse value, value not supported for option retry.", + ) + .add_context("value", value) + .add_context("option", "retry") + .source(err.into()) + })?, }; backoff.max_retries = max_retries; } else if option == "timeout" { - let timeout = match humantime::Duration::from_str(&value) { - Ok(val) => val, - Err(e) => return Err(RestErrorKind::CouldNotParseDuration(e).into()), - }; - client = match ClientBuilder::new().timeout(*timeout).build() { - Ok(val) => val, - Err(err) => return Err(RestErrorKind::BuildingClientFailed(err).into()), - }; + let timeout = humantime::Duration::from_str(&value).map_err(|err| { + RusticError::new( + ErrorKind::Parsing, + "Could not parse value as `humantime` duration.", + ) + .add_context("value", value) + .add_context("option", "timeout") + .source(err.into()) + })?; + + client = ClientBuilder::new() + .timeout(*timeout) + .build() + .map_err(|err| { + RusticError::new(ErrorKind::Backend, "Failed to build HTTP client") + .source(err.into()) + })?; } } @@ -218,7 +239,7 @@ impl RestBackend { /// /// # Errors /// - /// If the url could not be created. + /// * If the url could not be created. fn url(&self, tpe: FileType, id: &Id) -> Result { let id_path = if tpe == FileType::Config { "config".to_string() @@ -229,10 +250,10 @@ impl RestBackend { path.push_str(&hex_id); path }; - Ok(self - .url + + self.url .join(&id_path) - .map_err(RestErrorKind::JoiningUrlFailed)?) + .map_err(RestErrorKind::JoiningUrlFailed) } } @@ -256,7 +277,7 @@ impl ReadBackend for RestBackend { /// /// # Errors /// - /// * [`RestErrorKind::JoiningUrlFailed`] - If the url could not be created. + /// * If the url could not be created. /// /// # Notes /// @@ -265,8 +286,6 @@ impl ReadBackend for RestBackend { /// # Returns /// /// A vector of tuples containing the id and size of the files. - /// - /// [`RestErrorKind::JoiningUrlFailed`]: RestErrorKind::JoiningUrlFailed fn list_with_size(&self, tpe: FileType) -> RusticResult> { // format which is delivered by the REST-service #[derive(Deserialize)] @@ -276,18 +295,24 @@ impl ReadBackend for RestBackend { } trace!("listing tpe: {tpe:?}"); - let url = if tpe == FileType::Config { - self.url - .join("config") - .map_err(RestErrorKind::JoiningUrlFailed)? + + // TODO: Explain why we need special handling here + let path = if tpe == FileType::Config { + "config".to_string() } else { let mut path = tpe.dirname().to_string(); path.push('/'); - self.url - .join(&path) - .map_err(RestErrorKind::JoiningUrlFailed)? + path }; + let url = self.url.join(&path).map_err(|err| { + RusticError::new(ErrorKind::Parsing, "Joining URL failed") + .add_context("url", self.url.as_str()) + .add_context("tpe", tpe.to_string()) + .add_context("tpe dir", tpe.dirname().to_string()) + .source(err.into()) + })?; + backoff::retry_notify( self.backoff.clone(), || { @@ -319,7 +344,7 @@ impl ReadBackend for RestBackend { }, notify, ) - .map_err(|e| RestErrorKind::BackoffError(e).into()) + .map_err(construct_backoff_error) } /// Returns the content of a file. @@ -331,14 +356,16 @@ impl ReadBackend for RestBackend { /// /// # Errors /// - /// * [`reqwest::Error`] - If the request failed. - /// * [`RestErrorKind::BackoffError`] - If the backoff failed. - /// - /// [`RestErrorKind::BackoffError`]: RestErrorKind::BackoffError + /// * If the request failed. + /// * If the backoff failed. fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult { trace!("reading tpe: {tpe:?}, id: {id}"); - let url = self.url(tpe, id)?; - Ok(backoff::retry_notify( + + let url = self + .url(tpe, id) + .map_err(|err| construct_join_url_error(err, tpe, id, &self.url))?; + + backoff::retry_notify( self.backoff.clone(), || { Ok(self @@ -350,7 +377,7 @@ impl ReadBackend for RestBackend { }, notify, ) - .map_err(RestErrorKind::BackoffError)?) + .map_err(construct_backoff_error) } /// Returns a part of the content of a file. @@ -379,8 +406,16 @@ impl ReadBackend for RestBackend { trace!("reading tpe: {tpe:?}, id: {id}, offset: {offset}, length: {length}"); let offset2 = offset + length - 1; let header_value = format!("bytes={offset}-{offset2}"); - let url = self.url(tpe, id)?; - Ok(backoff::retry_notify( + let url = self.url(tpe, id).map_err(|err| { + RusticError::new(ErrorKind::Parsing, "Joining URL failed") + .add_context("url", self.url.as_str()) + .add_context("tpe", tpe.to_string()) + .add_context("tpe dir", tpe.dirname().to_string()) + .add_context("id", id.to_string()) + .source(err.into()) + })?; + + backoff::retry_notify( self.backoff.clone(), || { Ok(self @@ -393,24 +428,47 @@ impl ReadBackend for RestBackend { }, notify, ) - .map_err(RestErrorKind::BackoffError)?) + .map_err(construct_backoff_error) } } +fn construct_backoff_error(err: backoff::Error) -> RusticError { + RusticError::new( + ErrorKind::Backend, + "Backoff failed, please check the logs for more information.", + ) + .source(err.into()) +} + +fn construct_join_url_error( + err: RestErrorKind, + tpe: FileType, + id: &Id, + self_url: &Url, +) -> RusticError { + RusticError::new(ErrorKind::Parsing, "Joining URL failed") + .add_context("url", self_url.as_str()) + .add_context("tpe", tpe.to_string()) + .add_context("tpe dir", tpe.dirname().to_string()) + .add_context("id", id.to_string()) + .source(err.into()) +} + impl WriteBackend for RestBackend { /// Creates a new file. /// /// # Errors /// - /// * [`RestErrorKind::BackoffError`] - If the backoff failed. - /// - /// [`RestErrorKind::BackoffError`]: RestErrorKind::BackoffError + /// * If the backoff failed. fn create(&self) -> RusticResult<()> { - let url = self - .url - .join("?create=true") - .map_err(RestErrorKind::JoiningUrlFailed)?; - Ok(backoff::retry_notify( + let url = self.url.join("?create=true").map_err(|err| { + RusticError::new(ErrorKind::Parsing, "Joining URL failed") + .add_context("url", self.url.as_str()) + .add_context("join input", "?create=true") + .source(err.into()) + })?; + + backoff::retry_notify( self.backoff.clone(), || { _ = self.client.post(url.clone()).send()?.check_error()?; @@ -418,7 +476,7 @@ impl WriteBackend for RestBackend { }, notify, ) - .map_err(RestErrorKind::BackoffError)?) + .map_err(construct_backoff_error) } /// Writes bytes to the given file. @@ -432,9 +490,7 @@ impl WriteBackend for RestBackend { /// /// # Errors /// - /// * [`RestErrorKind::BackoffError`] - If the backoff failed. - /// - /// [`RestErrorKind::BackoffError`]: RestErrorKind::BackoffError + /// * If the backoff failed. fn write_bytes( &self, tpe: FileType, @@ -443,8 +499,15 @@ impl WriteBackend for RestBackend { buf: Bytes, ) -> RusticResult<()> { trace!("writing tpe: {:?}, id: {}", &tpe, &id); - let req_builder = self.client.post(self.url(tpe, id)?).body(buf); - Ok(backoff::retry_notify( + let req_builder = self + .client + .post( + self.url(tpe, id) + .map_err(|err| construct_join_url_error(err, tpe, id, &self.url))?, + ) + .body(buf); + + backoff::retry_notify( self.backoff.clone(), || { // Note: try_clone() always gives Some(_) as the body is Bytes which is cloneable @@ -453,7 +516,7 @@ impl WriteBackend for RestBackend { }, notify, ) - .map_err(RestErrorKind::BackoffError)?) + .map_err(construct_backoff_error) } /// Removes the given file. @@ -466,13 +529,14 @@ impl WriteBackend for RestBackend { /// /// # Errors /// - /// * [`RestErrorKind::BackoffError`] - If the backoff failed. - /// - /// [`RestErrorKind::BackoffError`]: RestErrorKind::BackoffError + /// * If the backoff failed. fn remove(&self, tpe: FileType, id: &Id, _cacheable: bool) -> RusticResult<()> { trace!("removing tpe: {:?}, id: {}", &tpe, &id); - let url = self.url(tpe, id)?; - Ok(backoff::retry_notify( + let url = self + .url(tpe, id) + .map_err(|err| construct_join_url_error(err, tpe, id, &self.url))?; + + backoff::retry_notify( self.backoff.clone(), || { _ = self.client.delete(url.clone()).send()?.check_error()?; @@ -480,6 +544,6 @@ impl WriteBackend for RestBackend { }, notify, ) - .map_err(RestErrorKind::BackoffError)?) + .map_err(construct_backoff_error) } } From 27784b20f715f96bf66f2cf05760b67c173e5bdf Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Fri, 25 Oct 2024 19:18:40 +0200 Subject: [PATCH 034/129] impl Display for FileType Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/backend.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/core/src/backend.rs b/crates/core/src/backend.rs index 9ca12b95..55240f3d 100644 --- a/crates/core/src/backend.rs +++ b/crates/core/src/backend.rs @@ -103,7 +103,7 @@ pub const ALL_FILE_TYPES: [FileType; 4] = [ ]; /// Type for describing the kind of a file that can occur. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Enum)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Enum, derive_more::Display)] pub enum FileType { /// Config file #[serde(rename = "config")] From ae1c33d0331cb19cf4ef0b96367be0b5ef290b96 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Fri, 25 Oct 2024 19:18:53 +0200 Subject: [PATCH 035/129] fix tests for error display and dbg Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/error.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index b6d96991..ad008086 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -342,18 +342,19 @@ mod tests { use super::*; - static TEST_ERROR: LazyLock = LazyLock::new(|| RusticError { + static TEST_ERROR: LazyLock = LazyLock::new(|| RusticError { kind: ErrorKind::Io, guidance: "A file could not be read, make sure the file is existing and readable by the system." - .to_string(), + .into(), status: Some(Status::Permanent), severity: Some(Severity::Error), code: Some("E001".to_string().into()), context: vec![ - ("path", "/path/to/file".to_string()), - ("called", "used s3 backend".to_string()), - ], + ("path", "/path/to/file".into()), + ("called", "used s3 backend".into()), + ] + .into_boxed_slice(), source: Some(Box::new(std::io::Error::new( std::io::ErrorKind::Other, "networking error", @@ -366,11 +367,11 @@ mod tests { #[test] fn test_error_display() { - todo!("Implement test_error_display"); + insta::assert_snapshot!(TEST_ERROR.to_string()); } #[test] fn test_error_debug() { - todo!("Implement test_error_debug"); + insta::assert_debug_snapshot!(TEST_ERROR); } } From a28089d2eb4bba7cb8251b19aaf9b638f4865556 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Fri, 25 Oct 2024 19:53:34 +0200 Subject: [PATCH 036/129] test error display and debug impl Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/backend/decrypt.rs | 4 +- crates/core/src/error.rs | 50 ++----------------- crates/core/src/lib.rs | 2 +- crates/core/src/repofile/keyfile.rs | 4 +- crates/core/tests/errors.rs | 29 +++++++++++ .../tests/snapshots/errors__error_debug.snap | 39 +++++++++++++++ .../snapshots/errors__error_display.snap | 25 ++++++++++ 7 files changed, 102 insertions(+), 51 deletions(-) create mode 100644 crates/core/tests/errors.rs create mode 100644 crates/core/tests/snapshots/errors__error_debug.snap create mode 100644 crates/core/tests/snapshots/errors__error_display.snap diff --git a/crates/core/src/backend/decrypt.rs b/crates/core/src/backend/decrypt.rs index 00f00f86..dcb29ba6 100644 --- a/crates/core/src/backend/decrypt.rs +++ b/crates/core/src/backend/decrypt.rs @@ -541,9 +541,7 @@ impl DecryptReadBackend for DecryptBackend { /// /// A vector containing the decrypted data. fn decrypt(&self, data: &[u8]) -> RusticResult> { - self.key - .decrypt_data(data) - .map_err(|_err| todo!("Error transition")) + self.key.decrypt_data(data) } /// Reads encrypted data from the backend. diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index ad008086..e9cdbcdf 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -61,12 +61,12 @@ pub struct RusticError { impl Display for RusticError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "An error occurred in `rustic_core`: {}", self.kind)?; + write!(f, "{} occurred in `rustic_core`", self.kind)?; - write!(f, "\nMessage: {}", self.guidance)?; + write!(f, "\n\nMessage:\n{}", self.guidance)?; if !self.context.is_empty() { - write!(f, "\n\n Context:\n")?; + write!(f, "\n\nContext:\n")?; write!( f, "{}", @@ -74,7 +74,7 @@ impl Display for RusticError { .iter() .map(|(k, v)| format!("{k}: {v}")) .collect::>() - .join(", ") + .join(",\n") )?; } @@ -94,7 +94,7 @@ impl Display for RusticError { let default_docs_url = SmolStr::from(constants::DEFAULT_DOCS_URL); let docs_url = self.docs_url.as_ref().unwrap_or(&default_docs_url); - write!(f, "\n\nFor more information, see: {docs_url}/{code}")?; + write!(f, "\n\nFor more information, see: {docs_url}{code}")?; } if let Some(existing_issue_url) = &self.existing_issue_url { @@ -335,43 +335,3 @@ pub enum ErrorKind { // - **Backend Access Errors**: e.g., `BackendNotSupported`, `BackendLoadError`, `NoSuitableIdFound`, `IdNotUnique` // - **Rclone Errors**: e.g., `NoOutputForRcloneVersion`, `NoStdOutForRclone`, `RCloneExitWithBadStatus` // - **REST API Errors**: e.g., `NotSupportedForRetry`, `UrlParsingFailed` - -#[cfg(test)] -mod tests { - use std::sync::LazyLock; - - use super::*; - - static TEST_ERROR: LazyLock = LazyLock::new(|| RusticError { - kind: ErrorKind::Io, - guidance: - "A file could not be read, make sure the file is existing and readable by the system." - .into(), - status: Some(Status::Permanent), - severity: Some(Severity::Error), - code: Some("E001".to_string().into()), - context: vec![ - ("path", "/path/to/file".into()), - ("called", "used s3 backend".into()), - ] - .into_boxed_slice(), - source: Some(Box::new(std::io::Error::new( - std::io::ErrorKind::Other, - "networking error", - ))), - backtrace: Some(Backtrace::disabled()), - docs_url: None, - new_issue_url: None, - existing_issue_url: None, - }); - - #[test] - fn test_error_display() { - insta::assert_snapshot!(TEST_ERROR.to_string()); - } - - #[test] - fn test_error_debug() { - insta::assert_debug_snapshot!(TEST_ERROR); - } -} diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 16a0cdb6..161cb018 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -145,7 +145,7 @@ pub use crate::{ repoinfo::{BlobInfo, IndexInfos, PackInfo, RepoFileInfo, RepoFileInfos}, restore::{FileDirStats, RestoreOptions, RestorePlan, RestoreStats}, }, - error::{ErrorKind, RusticError, RusticResult, Severity}, + error::{ErrorKind, RusticError, RusticResult, Severity, Status}, id::{HexId, Id}, progress::{NoProgress, NoProgressBars, Progress, ProgressBars}, repofile::snapshotfile::{ diff --git a/crates/core/src/repofile/keyfile.rs b/crates/core/src/repofile/keyfile.rs index 605799f0..7e001093 100644 --- a/crates/core/src/repofile/keyfile.rs +++ b/crates/core/src/repofile/keyfile.rs @@ -259,14 +259,14 @@ impl KeyFile { fn from_backend(be: &B, id: &KeyId) -> RusticResult { let data = be.read_full(FileType::Key, id)?; - Ok(serde_json::from_slice(&data) + serde_json::from_slice(&data) .map_err( |err| KeyFileErrorKind::DeserializingFromSliceForKeyIdFailed { key_id: id.clone(), source: err, }, ) - .map_err(|_err| todo!("Error transition"))?) + .map_err(|_err| todo!("Error transition")) } } diff --git a/crates/core/tests/errors.rs b/crates/core/tests/errors.rs new file mode 100644 index 00000000..8dc2d35e --- /dev/null +++ b/crates/core/tests/errors.rs @@ -0,0 +1,29 @@ +use rstest::{fixture, rstest}; +use std::backtrace::Backtrace; + +use rustic_core::{ErrorKind, RusticError, Severity, Status}; + +#[fixture] +fn error() -> RusticError { + RusticError::new( + ErrorKind::Io, + "A file could not be read, make sure the file is existing and readable by the system.", + ) + .status(Status::Permanent) + .severity(Severity::Error) + .code("E001".into()) + .add_context("path", "/path/to/file") + .add_context("called", "used s3 backend") + .source(std::io::Error::new(std::io::ErrorKind::Other, "networking error").into()) + .backtrace(Backtrace::disabled()) +} + +#[rstest] +fn test_error_display(error: RusticError) { + insta::assert_snapshot!(error); +} + +#[rstest] +fn test_error_debug(error: RusticError) { + insta::assert_debug_snapshot!(error); +} diff --git a/crates/core/tests/snapshots/errors__error_debug.snap b/crates/core/tests/snapshots/errors__error_debug.snap new file mode 100644 index 00000000..d6c7f45f --- /dev/null +++ b/crates/core/tests/snapshots/errors__error_debug.snap @@ -0,0 +1,39 @@ +--- +source: crates/core/tests/errors.rs +expression: error +--- +RusticError { + kind: Io, + source: Some( + Custom { + kind: Other, + error: "networking error", + }, + ), + guidance: "A file could not be read, make sure the file is existing and readable by the system.", + context: [ + ( + "path", + "/path/to/file", + ), + ( + "called", + "used s3 backend", + ), + ], + docs_url: None, + code: Some( + "E001", + ), + new_issue_url: None, + existing_issue_url: None, + severity: Some( + Error, + ), + status: Some( + Permanent, + ), + backtrace: Some( + , + ), +} diff --git a/crates/core/tests/snapshots/errors__error_display.snap b/crates/core/tests/snapshots/errors__error_display.snap new file mode 100644 index 00000000..62a7651f --- /dev/null +++ b/crates/core/tests/snapshots/errors__error_display.snap @@ -0,0 +1,25 @@ +--- +source: crates/core/tests/errors.rs +expression: TEST_ERROR.to_string() +--- +IO Error occurred in `rustic_core` + +Message: +A file could not be read, make sure the file is existing and readable by the system. + +Context: +path: /path/to/file, +called: used s3 backend + +Caused by: networking error + +Severity: Error + +Status: Permanent + +For more information, see: https://rustic.cli.rs/docs/errors/E001 + +If you think this is an undiscovered bug, please open an issue at: https://github.com/rustic-rs/rustic_core/issues/new + +Backtrace: + From 9b7c3470be9fdc427387fdad40ff158075f3cf2b Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sat, 26 Oct 2024 17:23:06 +0200 Subject: [PATCH 037/129] Migrate to `with_source` constructor Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/choose.rs | 6 +- crates/backend/src/local.rs | 150 +++--- crates/backend/src/opendal.rs | 13 +- crates/backend/src/rclone.rs | 47 +- crates/backend/src/rest.rs | 73 ++- crates/backend/src/util.rs | 6 +- crates/core/src/archiver.rs | 4 +- crates/core/src/archiver/file_archiver.rs | 10 +- crates/core/src/backend.rs | 5 +- crates/core/src/backend/childstdout.rs | 12 +- crates/core/src/backend/stdin.rs | 12 +- crates/core/src/chunker.rs | 20 +- crates/core/src/commands/cat.rs | 2 +- crates/core/src/commands/check.rs | 36 +- crates/core/src/commands/dump.rs | 2 +- crates/core/src/commands/prune.rs | 16 +- crates/core/src/commands/restore.rs | 24 +- crates/core/src/crypto/aespoly1305.rs | 13 +- crates/core/src/error.rs | 176 ++++++- crates/core/src/id.rs | 11 +- crates/core/src/index.rs | 4 +- crates/core/src/repofile/keyfile.rs | 4 +- crates/core/src/repository.rs | 45 +- crates/core/src/vfs.rs | 73 ++- crates/core/tests/errors.rs | 6 +- crates/testing/src/backend.rs | 4 +- error_expanded.rs | 560 ++++++++++++++++++++++ 27 files changed, 1057 insertions(+), 277 deletions(-) create mode 100644 error_expanded.rs diff --git a/crates/backend/src/choose.rs b/crates/backend/src/choose.rs index 685def7f..9e246189 100644 --- a/crates/backend/src/choose.rs +++ b/crates/backend/src/choose.rs @@ -117,12 +117,12 @@ impl BackendOptions { .map(|string| { let (be_type, location) = location_to_type_and_path(string)?; be_type.to_backend(location, options.into()).map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Backend, "Could not load the backend. Please check the given backend and try again.", + err, ) - .add_context("name", be_type.to_string()) - .source(err.into()) + .attach_context("name", be_type.to_string()) }) }) .transpose() diff --git a/crates/backend/src/local.rs b/crates/backend/src/local.rs index bf6cade7..ea8a75b4 100644 --- a/crates/backend/src/local.rs +++ b/crates/backend/src/local.rs @@ -168,9 +168,10 @@ impl LocalBackend { let patterns = &["%file", "%type", "%id"]; let ac = AhoCorasick::new(patterns).map_err(|err| { - RusticError::new(ErrorKind::Backend, - "Experienced an error building AhoCorasick automaton for command replacement. This is a bug. Please report it.") - .source(err.into()) + RusticError::with_source( + ErrorKind::Backend, + "Experienced an error building AhoCorasick automaton for command replacement. This is a bug. Please report it.", + err) })?; let replace_with = &[filename.to_str().unwrap(), tpe.dirname(), id.as_str()]; @@ -183,34 +184,34 @@ impl LocalBackend { actual_command .parse() .map_err(|err: CommandInputErrorKind| { - RusticError::new( + RusticError::with_source( ErrorKind::Parsing, "Failed to parse command input. This is a bug. Please report it.", + err, ) - .add_context("command", actual_command) - .add_context("replacement", replace_with.join(", ")) - .source(err.into()) + .attach_context("command", actual_command) + .attach_context("replacement", replace_with.join(", ")) })?; let status = Command::new(command.command()) .args(command.args()) .status() .map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Command, "Failed to execute command. Please check the command and try again.", + err, ) - .add_context("command", command.to_string()) - .source(err.into()) + .attach_context("command", command.to_string()) })?; if !status.success() { return Err( RusticError::new(ErrorKind::Command, "Command was not successful.") - .add_context("file_name", replace_with[0]) - .add_context("file_type", replace_with[1]) - .add_context("id", replace_with[2]) - .add_context("status", status.to_string()), + .attach_context("file_name", replace_with[0]) + .attach_context("file_type", replace_with[1]) + .attach_context("id", replace_with[2]) + .attach_context("status", status.to_string()), ); } Ok(()) @@ -279,22 +280,22 @@ impl ReadBackend for LocalBackend { Id::default(), path.metadata() .map_err(|err| - RusticError::new( + RusticError::with_source( ErrorKind::Backend, "Failed to query metadata of the file. Please check the file and try again.", + err ) - .add_context("path", path.to_string_lossy()) - .source(err.into()) + .attach_context("path", path.to_string_lossy()) )? .len() .try_into() .map_err(|err: TryFromIntError| - RusticError::new( + RusticError::with_source( ErrorKind::Backend, "Failed to convert file length to u32. This is a bug. Please report it.", + err ) - .add_context("length", path.metadata().unwrap().len().to_string()) - .source(err.into()) + .attach_context("length", path.metadata().unwrap().len().to_string()) )?, )] } else { @@ -311,23 +312,23 @@ impl ReadBackend for LocalBackend { e.file_name().to_string_lossy().parse()?, e.metadata() .map_err(|err| - RusticError::new( + RusticError::with_source( ErrorKind::Backend, "Failed to query metadata of the file. Please check the file and try again.", + err ) - .add_context("path", e.path().to_string_lossy()) - .source(err.into()) + .attach_context("path", e.path().to_string_lossy()) ) ? .len() .try_into() .map_err(|err: TryFromIntError| - RusticError::new( + RusticError::with_source( ErrorKind::Backend, "Failed to convert file length to u32. This is a bug. Please report it.", + err ) - .add_context("length", e.metadata().unwrap().len().to_string()) - .source(err.into()) + .attach_context("length", e.metadata().unwrap().len().to_string()) )?, )) }) @@ -351,12 +352,12 @@ impl ReadBackend for LocalBackend { trace!("reading tpe: {tpe:?}, id: {id}"); Ok(fs::read(self.path(tpe, id)) .map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Backend, "Failed to read the contents of the file. Please check the file and try again.", + err, ) - .add_context("path", self.path(tpe, id).to_string_lossy()) - .source(err.into()) + .attach_context("path", self.path(tpe, id).to_string_lossy()) })? .into()) } @@ -384,43 +385,43 @@ impl ReadBackend for LocalBackend { ) -> RusticResult { trace!("reading tpe: {tpe:?}, id: {id}, offset: {offset}, length: {length}"); let mut file = File::open(self.path(tpe, id)).map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Backend, "Failed to open the file. Please check the file and try again.", + err, ) - .add_context("path", self.path(tpe, id).to_string_lossy()) - .source(err.into()) + .attach_context("path", self.path(tpe, id).to_string_lossy()) })?; _ = file.seek(SeekFrom::Start(offset.into())).map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Backend, "Failed to seek to the position in the file. Please check the file and try again.", + err, ) - .add_context("path", self.path(tpe, id).to_string_lossy()) - .add_context("offset", offset.to_string()) - .source(err.into()) + .attach_context("path", self.path(tpe, id).to_string_lossy()) + .attach_context("offset", offset.to_string()) })?; let mut vec = vec![ 0; - length - .try_into() - .map_err(|err: TryFromIntError| RusticError::new( + length.try_into().map_err(|err: TryFromIntError| { + RusticError::with_source( ErrorKind::Backend, "Failed to convert length to u64. This is a bug. Please report it.", + err, ) - .add_context("length", length.to_string()) - .source(err.into()))? + .attach_context("length", length.to_string()) + })? ]; file.read_exact(&mut vec).map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Backend, "Failed to read the exact length of the file. Please check the file and try again.", + err, ) - .add_context("path", self.path(tpe, id).to_string_lossy()) - .add_context("length", length.to_string()) - .source(err.into()) + .attach_context("path", self.path(tpe, id).to_string_lossy()) + .attach_context("length", length.to_string()) })?; Ok(vec.into()) @@ -438,35 +439,35 @@ impl WriteBackend for LocalBackend { fn create(&self) -> RusticResult<()> { trace!("creating repo at {:?}", self.path); fs::create_dir_all(&self.path).map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Io, "Failed to create the directory. Please check the path and try again.", + err, ) - .add_context("path", self.path.display().to_string()) - .source(err.into()) + .attach_context("path", self.path.display().to_string()) })?; for tpe in ALL_FILE_TYPES { let path = self.path.join(tpe.dirname()); fs::create_dir_all(path.clone()).map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Io, "Failed to create the directory. Please check the path and try again.", + err, ) - .add_context("path", path.display().to_string()) - .source(err.into()) + .attach_context("path", path.display().to_string()) })?; } for i in 0u8..=255 { let path = self.path.join("data").join(hex::encode([i])); fs::create_dir_all(path.clone()).map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Io, "Failed to create the directory. Please check the path and try again.", + err, ) - .add_context("path", path.display().to_string()) - .source(err.into()) + .attach_context("path", path.display().to_string()) })?; } Ok(()) @@ -492,51 +493,56 @@ impl WriteBackend for LocalBackend { ) -> RusticResult<()> { trace!("writing tpe: {:?}, id: {}", &tpe, &id); let filename = self.path(tpe, id); + let mut file = fs::OpenOptions::new() .create(true) .truncate(true) .write(true) .open(&filename) .map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Backend, "Failed to open the file. Please check the file and try again.", + err, ) - .add_context("path", filename.to_string_lossy()) - .source(err.into()) + .attach_context("path", filename.to_string_lossy()) })?; + file.set_len(buf.len().try_into().map_err(|err: TryFromIntError| { - RusticError::new( + RusticError::with_source( ErrorKind::Backend, "Failed to convert length to u64. This is a bug. Please report it.", + err, ) - .add_context("length", buf.len().to_string()) - .source(err.into()) + .attach_context("length", buf.len().to_string()) })?) .map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Backend, "Failed to set the length of the file. Please check the file and try again.", + err, ) - .add_context("path", filename.to_string_lossy()) - .source(err.into()) + .attach_context("path", filename.to_string_lossy()) })?; + file.write_all(&buf).map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Backend, "Failed to write to the buffer. Please check the file and try again.", + err, ) - .add_context("path", filename.to_string_lossy()) - .source(err.into()) + .attach_context("path", filename.to_string_lossy()) })?; + file.sync_all().map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Backend, "Failed to sync OS Metadata to disk. Please check the file and try again.", + err, ) - .add_context("path", filename.to_string_lossy()) - .source(err.into()) + .attach_context("path", filename.to_string_lossy()) })?; + if let Some(command) = &self.post_create_command { if let Err(err) = Self::call_command(tpe, id, &filename, command) { warn!("post-create: {err}"); @@ -562,12 +568,12 @@ impl WriteBackend for LocalBackend { trace!("removing tpe: {:?}, id: {}", &tpe, &id); let filename = self.path(tpe, id); fs::remove_file(&filename).map_err(|err| - RusticError::new( + RusticError::with_source( ErrorKind::Backend, "Failed to remove the file. Was the file already removed or is it in use? Please check the file and remove it manually.", + err ) - .add_context("path", filename.to_string_lossy()) - .source(err.into()) + .attach_context("path", filename.to_string_lossy()) )?; if let Some(command) = &self.post_delete_command { if let Err(err) = Self::call_command(tpe, id, &filename, command) { diff --git a/crates/backend/src/opendal.rs b/crates/backend/src/opendal.rs index ce433e9d..1fd9b811 100644 --- a/crates/backend/src/opendal.rs +++ b/crates/backend/src/opendal.rs @@ -53,18 +53,21 @@ impl FromStr for Throttle { .split(',') .map(|s| { ByteSize::from_str(s.trim()).map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Parsing, "Parsing ByteSize from throttle string failed", + err, ) - .add_context("string", s) - .source(err.into()) + .attach_context("string", s) }) }) .map(|b| -> RusticResult { Ok(b?.as_u64().try_into().map_err(|err: TryFromIntError| { - RusticError::new(ErrorKind::Parsing, "Converting ByteSize to u32 failed") - .source(err.into()) + RusticError::with_source( + ErrorKind::Parsing, + "Converting ByteSize to u32 failed", + err, + ) })?) }); let bandwidth = values diff --git a/crates/backend/src/rclone.rs b/crates/backend/src/rclone.rs index e70d281e..74bcd21c 100644 --- a/crates/backend/src/rclone.rs +++ b/crates/backend/src/rclone.rs @@ -76,11 +76,11 @@ impl Drop for RcloneBackend { fn check_clone_version(rclone_version_output: &[u8]) -> RusticResult<()> { let rclone_version = std::str::from_utf8(rclone_version_output) .map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Parsing, "Expected rclone version to be valid utf8, but it was not.", + err ) - .source(err.into()) })? .lines() .next() @@ -93,9 +93,8 @@ fn check_clone_version(rclone_version_output: &[u8]) -> RusticResult<()> { .trim_start_matches(|c: char| !c.is_numeric()); let mut parsed_version = Version::parse(rclone_version).map_err(|err| { - RusticError::new(ErrorKind::Parsing, "Error parsing rclone version.") - .add_context("version", rclone_version) - .source(err.into()) + RusticError::with_source(ErrorKind::Parsing, "Error parsing rclone version.", err) + .attach_context("version", rclone_version) })?; // we need to set the pre and build fields to empty to make the comparison work @@ -111,11 +110,11 @@ fn check_clone_version(rclone_version_output: &[u8]) -> RusticResult<()> { // reasonable lower bound for the version if VersionReq::parse("<1.52.2") .map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Parsing, "Error parsing version requirement. This should not happen.", + err, ) - .source(err.into()) })? .matches(&parsed_version) { @@ -123,7 +122,7 @@ fn check_clone_version(rclone_version_output: &[u8]) -> RusticResult<()> { ErrorKind::Unsupported, "Unsupported rclone version. We must not use rclone without authentication! Please upgrade to rclone >= 1.52.2!", ) - .add_context("current version", rclone_version.to_string())); + .attach_context("current version", rclone_version.to_string())); } Ok(()) @@ -161,11 +160,11 @@ impl RcloneBackend { let use_password = options .get("use-password") .map(|v| v.parse().map_err(|err: ParseBoolError| - RusticError::new( + RusticError::with_source( ErrorKind::Parsing, "Expected 'use-password' to be a boolean, but it was not. Please check the configuration file.", + err ) - .source(err.into()) )) .transpose()? .unwrap_or(true); @@ -174,10 +173,11 @@ impl RcloneBackend { let rclone_version_output = Command::new("rclone") .arg("version") .output() - .map_err(|err| RusticError::new( + .map_err(|err| RusticError::with_source( ErrorKind::ExternalCommand, "Experienced an error while running `rclone version` command. Please check if rclone is installed and in your PATH.", - ).source(err.into()))? + err + ))? .stdout; // if we want to use a password and rclone_command is not explicitly set, @@ -193,11 +193,11 @@ impl RcloneBackend { rclone_command.push(' '); rclone_command.push_str(url.as_ref()); let rclone_command: CommandInput = rclone_command.parse().map_err( - |err: CommandInputErrorKind| RusticError::new( + |err: CommandInputErrorKind| RusticError::with_source( ErrorKind::Parsing, "Expected rclone command to be valid, but it was not. Please check the configuration file.", + err ) - .source(err.into()) )?; debug!("starting rclone via {rclone_command:?}"); @@ -214,12 +214,13 @@ impl RcloneBackend { .stderr(Stdio::piped()) .spawn() .map_err(|err| - RusticError::new( + RusticError::with_source( ErrorKind::ExternalCommand, "Experienced an error while running rclone. Please check if rclone is installed and working correctly.", + err ) - .add_context("rclone command", rclone_command.to_string()) - .source(err.into()))?; + .attach_context("rclone command", rclone_command.to_string()) + )?; let mut stderr = BufReader::new( child @@ -235,17 +236,17 @@ impl RcloneBackend { None => { loop { if let Some(status) = child.try_wait().map_err(|err| - RusticError::new( + RusticError::with_source( ErrorKind::ExternalCommand, "Experienced an error while running rclone. Please check if rclone is installed and working correctly.", + err ) - .source(err.into()) )? { return Err( RusticError::new( ErrorKind::ExternalCommand, "rclone exited before it could start the REST server. Please check the exit status for more information.", - ).add_context("exit status", status.to_string()) + ).attach_context("exit status", status.to_string()) ); } let mut line = String::new(); @@ -253,11 +254,11 @@ impl RcloneBackend { _ = stderr .read_line(&mut line) .map_err(|err| - RusticError::new( + RusticError::with_source( ErrorKind::Io, "Experienced an error while reading rclone output. Please check if rclone is installed and working correctly.", + err ) - .source(err.into()) )?; match line.find(constants::SEARCHSTRING) { @@ -282,7 +283,7 @@ impl RcloneBackend { ErrorKind::Io, "Please make sure, the URL starts with 'http://'!", ) - .add_context("url", rest_url)); + .attach_context("url", rest_url)); } rest_url = format!("http://{user}:{password}@{}", &rest_url[7..]); } diff --git a/crates/backend/src/rest.rs b/crates/backend/src/rest.rs index c4a8f391..63598edb 100644 --- a/crates/backend/src/rest.rs +++ b/crates/backend/src/rest.rs @@ -167,9 +167,8 @@ impl RestBackend { }; let url = Url::parse(&url).map_err(|err| { - RusticError::new(ErrorKind::Parsing, "URL parsing failed") - .add_context("url", url) - .source(err.into()) + RusticError::with_source(ErrorKind::Parsing, "URL parsing failed", err) + .attach_context("url", url) })?; let mut headers = HeaderMap::new(); @@ -180,8 +179,7 @@ impl RestBackend { .timeout(constants::DEFAULT_TIMEOUT) // set default timeout to 10 minutes (we can have *large* packfiles) .build() .map_err(|err| { - RusticError::new(ErrorKind::Backend, "Failed to build HTTP client") - .source(err.into()) + RusticError::with_source(ErrorKind::Backend, "Failed to build HTTP client", err) })?; let mut backoff = LimitRetryBackoff::default(); @@ -192,33 +190,36 @@ impl RestBackend { "false" | "off" => 0, "default" => constants::DEFAULT_RETRY, _ => usize::from_str(&value).map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Parsing, "Cannot parse value, value not supported for option retry.", + err, ) - .add_context("value", value) - .add_context("option", "retry") - .source(err.into()) + .attach_context("value", value) + .attach_context("option", "retry") })?, }; backoff.max_retries = max_retries; } else if option == "timeout" { let timeout = humantime::Duration::from_str(&value).map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Parsing, "Could not parse value as `humantime` duration.", + err, ) - .add_context("value", value) - .add_context("option", "timeout") - .source(err.into()) + .attach_context("value", value) + .attach_context("option", "timeout") })?; client = ClientBuilder::new() .timeout(*timeout) .build() .map_err(|err| { - RusticError::new(ErrorKind::Backend, "Failed to build HTTP client") - .source(err.into()) + RusticError::with_source( + ErrorKind::Backend, + "Failed to build HTTP client", + err, + ) })?; } } @@ -306,11 +307,10 @@ impl ReadBackend for RestBackend { }; let url = self.url.join(&path).map_err(|err| { - RusticError::new(ErrorKind::Parsing, "Joining URL failed") - .add_context("url", self.url.as_str()) - .add_context("tpe", tpe.to_string()) - .add_context("tpe dir", tpe.dirname().to_string()) - .source(err.into()) + RusticError::with_source(ErrorKind::Parsing, "Joining URL failed", err) + .attach_context("url", self.url.as_str()) + .attach_context("tpe", tpe.to_string()) + .attach_context("tpe dir", tpe.dirname().to_string()) })?; backoff::retry_notify( @@ -407,12 +407,11 @@ impl ReadBackend for RestBackend { let offset2 = offset + length - 1; let header_value = format!("bytes={offset}-{offset2}"); let url = self.url(tpe, id).map_err(|err| { - RusticError::new(ErrorKind::Parsing, "Joining URL failed") - .add_context("url", self.url.as_str()) - .add_context("tpe", tpe.to_string()) - .add_context("tpe dir", tpe.dirname().to_string()) - .add_context("id", id.to_string()) - .source(err.into()) + RusticError::with_source(ErrorKind::Parsing, "Joining URL failed", err) + .attach_context("url", self.url.as_str()) + .attach_context("tpe", tpe.to_string()) + .attach_context("tpe dir", tpe.dirname().to_string()) + .attach_context("id", id.to_string()) })?; backoff::retry_notify( @@ -433,11 +432,11 @@ impl ReadBackend for RestBackend { } fn construct_backoff_error(err: backoff::Error) -> RusticError { - RusticError::new( + RusticError::with_source( ErrorKind::Backend, "Backoff failed, please check the logs for more information.", + err, ) - .source(err.into()) } fn construct_join_url_error( @@ -446,12 +445,11 @@ fn construct_join_url_error( id: &Id, self_url: &Url, ) -> RusticError { - RusticError::new(ErrorKind::Parsing, "Joining URL failed") - .add_context("url", self_url.as_str()) - .add_context("tpe", tpe.to_string()) - .add_context("tpe dir", tpe.dirname().to_string()) - .add_context("id", id.to_string()) - .source(err.into()) + RusticError::with_source(ErrorKind::Parsing, "Joining URL failed", err) + .attach_context("url", self_url.as_str()) + .attach_context("tpe", tpe.to_string()) + .attach_context("tpe dir", tpe.dirname().to_string()) + .attach_context("id", id.to_string()) } impl WriteBackend for RestBackend { @@ -462,10 +460,9 @@ impl WriteBackend for RestBackend { /// * If the backoff failed. fn create(&self) -> RusticResult<()> { let url = self.url.join("?create=true").map_err(|err| { - RusticError::new(ErrorKind::Parsing, "Joining URL failed") - .add_context("url", self.url.as_str()) - .add_context("join input", "?create=true") - .source(err.into()) + RusticError::with_source(ErrorKind::Parsing, "Joining URL failed", err) + .attach_context("url", self.url.as_str()) + .attach_context("join input", "?create=true") })?; backoff::retry_notify( diff --git a/crates/backend/src/util.rs b/crates/backend/src/util.rs index fa0e1d39..138fb0b7 100644 --- a/crates/backend/src/util.rs +++ b/crates/backend/src/util.rs @@ -59,12 +59,12 @@ pub fn location_to_type_and_path( )), Some((scheme, path)) => Ok(( SupportedBackend::try_from(scheme).map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Parsing, "The backend type is not supported. Please check the given backend and try again.", + err ) - .add_context("name", scheme) - .source(err.into()) + .attach_context("name", scheme) })?, BackendLocation(path.to_string()), )), diff --git a/crates/core/src/archiver.rs b/crates/core/src/archiver.rs index a959b4f0..762d6f68 100644 --- a/crates/core/src/archiver.rs +++ b/crates/core/src/archiver.rs @@ -242,11 +242,11 @@ impl<'a, BE: DecryptFullBackend, I: ReadGlobalIndex> Archiver<'a, BE, I> { self.indexer.write().unwrap().finalize()?; summary.finalize(self.snap.time).map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Processing, "Could not finalize summary, please check the logs for more information.", + err, ) - .source(err.into()) })?; self.snap.summary = Some(summary); diff --git a/crates/core/src/archiver/file_archiver.rs b/crates/core/src/archiver/file_archiver.rs index eb428784..bb93a481 100644 --- a/crates/core/src/archiver/file_archiver.rs +++ b/crates/core/src/archiver/file_archiver.rs @@ -23,6 +23,7 @@ use crate::{ index::{indexer::SharedIndexer, ReadGlobalIndex}, progress::Progress, repofile::configfile::ConfigFile, + ErrorKind, RusticError, }; /// The `FileArchiver` is responsible for archiving files. @@ -144,7 +145,14 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> FileArchiver<'a, BE, I> { ) -> RusticResult<(Node, u64)> { let chunks: Vec<_> = ChunkIter::new( r, - usize::try_from(node.meta.size).map_err(|_err| todo!("Error transition"))?, + usize::try_from(node.meta.size).map_err(|err| { + RusticError::with_source( + ErrorKind::Conversion, + "Failed to convert node size to usize", + err, + ) + .attach_context("size", node.meta.size.to_string()) + })?, self.rabin.clone(), ) .map(|chunk| { diff --git a/crates/core/src/backend.rs b/crates/core/src/backend.rs index 55240f3d..7e10cea0 100644 --- a/crates/core/src/backend.rs +++ b/crates/core/src/backend.rs @@ -492,11 +492,10 @@ pub struct ReadSourceEntry { } impl ReadSourceEntry { - fn from_path(path: PathBuf, open: Option) -> RusticResult { + fn from_path(path: PathBuf, open: Option) -> BackendResult { let node = Node::new_node( path.file_name() - .ok_or_else(|| BackendErrorKind::PathNotAllowed(path.clone())) - .map_err(|_err| todo!("Error transition"))?, + .ok_or_else(|| BackendErrorKind::PathNotAllowed(path.clone()))?, NodeType::File, Metadata::default(), ); diff --git a/crates/core/src/backend/childstdout.rs b/crates/core/src/backend/childstdout.rs index 4debae0b..49b95dba 100644 --- a/crates/core/src/backend/childstdout.rs +++ b/crates/core/src/backend/childstdout.rs @@ -7,7 +7,7 @@ use std::{ use crate::{ backend::{ReadSource, ReadSourceEntry}, - error::RusticResult, + error::{ErrorKind, RusticError, RusticResult}, repository::command_input::{CommandInput, CommandInputErrorKind}, }; @@ -70,6 +70,14 @@ impl ReadSource for ChildStdoutSource { fn entries(&self) -> Self::Iter { let open = self.process.lock().unwrap().stdout.take(); - once(ReadSourceEntry::from_path(self.path.clone(), open)) + once( + ReadSourceEntry::from_path(self.path.clone(), open).map_err(|err| { + RusticError::with_source( + ErrorKind::Backend, + "Failed to create ReadSourceEntry from ChildStdout", + err, + ) + }), + ) } } diff --git a/crates/core/src/backend/stdin.rs b/crates/core/src/backend/stdin.rs index fc747936..c79543fc 100644 --- a/crates/core/src/backend/stdin.rs +++ b/crates/core/src/backend/stdin.rs @@ -6,7 +6,7 @@ use std::{ use crate::{ backend::{ReadSource, ReadSourceEntry}, - error::RusticResult, + error::{ErrorKind, RusticError, RusticResult}, }; /// The `StdinSource` is a `ReadSource` for stdin. @@ -37,6 +37,14 @@ impl ReadSource for StdinSource { /// Returns an iterator over the source. fn entries(&self) -> Self::Iter { let open = Some(stdin()); - once(ReadSourceEntry::from_path(self.path.clone(), open)) + once( + ReadSourceEntry::from_path(self.path.clone(), open).map_err(|err| { + RusticError::with_source( + ErrorKind::Backend, + "Failed to create ReadSourceEntry from Stdin", + err, + ) + }), + ) } } diff --git a/crates/core/src/chunker.rs b/crates/core/src/chunker.rs index af16bccc..60cc1096 100644 --- a/crates/core/src/chunker.rs +++ b/crates/core/src/chunker.rs @@ -96,9 +96,9 @@ impl ChunkIter { } impl Iterator for ChunkIter { - type Item = io::Result>; + type Item = RusticResult>; - fn next(&mut self) -> Option>> { + fn next(&mut self) -> Option { if self.finished { return None; } @@ -120,7 +120,13 @@ impl Iterator for ChunkIter { .read_to_end(&mut vec) { Ok(size) => size, - Err(err) => return Some(Err(err)), + Err(err) => { + return Some(Err(RusticError::with_source( + ErrorKind::Io, + "Failed to read from reader in iterator", + err, + ))); + } }; // If self.min_size is not reached, we are done. @@ -158,8 +164,12 @@ impl Iterator for ChunkIter { } Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue, - Err(e) => { - return Some(Err(e)); + Err(err) => { + return Some(Err(RusticError::with_source( + ErrorKind::Io, + "Failed to read from reader in iterator", + err, + ))); } } } diff --git a/crates/core/src/commands/cat.rs b/crates/core/src/commands/cat.rs index 97f0ffd2..1c10e765 100644 --- a/crates/core/src/commands/cat.rs +++ b/crates/core/src/commands/cat.rs @@ -118,7 +118,7 @@ pub(crate) fn cat_tree( ErrorKind::Command, "Path in Node subtree is not a directory. Please provide a directory path.", ) - .add_context("path", path.to_string()) + .attach_context("path", path.to_string()) })?; let data = repo .index() diff --git a/crates/core/src/commands/check.rs b/crates/core/src/commands/check.rs index 524b7226..6027d392 100644 --- a/crates/core/src/commands/check.rs +++ b/crates/core/src/commands/check.rs @@ -241,36 +241,36 @@ impl FromStr for ReadSubsetOption { } else if let Some(p) = s.strip_suffix('%') { // try to read percentage Self::Percentage(p.parse().map_err(|err: ParseFloatError| { - RusticError::new( + RusticError::with_source( ErrorKind::Parsing, "Error parsing percentage for ReadSubset option. Did you forget the '%'?", + err, ) - .add_context("value", p.to_string()) - .source(err.into()) + .attach_context("value", p.to_string()) })?) } else if let Some((n, m)) = s.split_once('/') { let now = Local::now().naive_local(); Self::IdSubSet(parse_n_m(now, n, m).map_err( |err| - RusticError::new( + RusticError::with_source( ErrorKind::Parsing, "Error parsing n/m for ReadSubset option. Allowed values: 'all', 'x%', 'n/m' or a size.", + err ) - .add_context("value", s) - .add_context("n/m", format!("{}/{}", n, m)) - .add_context("now", now.to_string()) - .source(err.into()) + .attach_context("value", s) + .attach_context("n/m", format!("{}/{}", n, m)) + .attach_context("now", now.to_string()) )?) } else { Self::Size( ByteSize::from_str(s) .map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Parsing, "Error parsing size for ReadSubset option. Allowed values: 'all', 'x%', 'n/m' or a size.", + err ) - .add_context("value", s) - .source(err.into()) + .attach_context("value", s) })? .as_u64(), ) @@ -777,13 +777,13 @@ fn check_pack( let header_len = PackHeaderRef::from_index_pack(&index_pack).size(); let pack_header_len = PackHeaderLength::from_binary(&data.split_off(data.len() - 4)) .map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Command, "Error reading pack header length. This is a bug. Please report this error.", + err, ) - .add_context("pack id", id.to_string()) - .add_context("header length", header_len.to_string()) - .source(err.into()) + .attach_context("pack id", id.to_string()) + .attach_context("header length", header_len.to_string()) })? .to_u32(); if pack_header_len != header_len { @@ -796,12 +796,12 @@ fn check_pack( let pack_blobs = PackHeader::from_binary(&header) .map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Command, "Error reading pack header. This is a bug. Please report this error.", + err, ) - .add_context("pack id", id.to_string()) - .source(err.into()) + .attach_context("pack id", id.to_string()) })? .into_blobs(); let mut blobs = index_pack.blobs; diff --git a/crates/core/src/commands/dump.rs b/crates/core/src/commands/dump.rs index 44b30f56..104ab6ef 100644 --- a/crates/core/src/commands/dump.rs +++ b/crates/core/src/commands/dump.rs @@ -36,7 +36,7 @@ pub(crate) fn dump( ErrorKind::Command, "Dump is not supported for non-file node types. You could try to use `cat` instead.", ) - .add_context("node type", node.node_type.to_string())); + .attach_context("node type", node.node_type.to_string())); } for id in node.content.as_ref().unwrap() { diff --git a/crates/core/src/commands/prune.rs b/crates/core/src/commands/prune.rs index 0c5577fd..d00d19e4 100644 --- a/crates/core/src/commands/prune.rs +++ b/crates/core/src/commands/prune.rs @@ -695,7 +695,7 @@ impl PrunePlan { ErrorKind::Config, "Repository is version 1, cannot repack uncompressed packs. ", ) - .add_context("config version", version.to_string())); + .attach_context("config version", version.to_string())); } let mut index_files = Vec::new(); @@ -788,7 +788,7 @@ impl PrunePlan { if *count == 0 { return Err( RusticError::new(ErrorKind::Command, "Blob is missing in index files, this should not happen. Please report this issue.") - .add_context("blob id", id.to_string()), + .attach_context("blob id", id.to_string()), ); } } @@ -1063,14 +1063,14 @@ impl PrunePlan { ErrorKind::Command, "Pack size does not match the size in the index file. This should not happen. Please report this issue.", ) - .add_context("pack id", pack.id.to_string()) - .add_context("size in index (expected)", pack.size.to_string()) - .add_context("size in pack (real)", size.to_string()) + .attach_context("pack id", pack.id.to_string()) + .attach_context("size in index (expected)", pack.size.to_string()) + .attach_context("size in pack (real)", size.to_string()) ), None => Err(RusticError::new( ErrorKind::Command, "Pack does not exist. This should not happen. Please report this issue.", - ).add_context("pack id", pack.id.to_string())), + ).attach_context("pack id", pack.id.to_string())), } }; @@ -1080,7 +1080,7 @@ impl PrunePlan { ErrorKind::Command, "Pack got no decision what to do with it, please report this!", ) - .add_context("pack id", pack.id.to_string())); + .attach_context("pack id", pack.id.to_string())); } PackToDo::Keep | PackToDo::Recover => { for blob in &pack.blobs { @@ -1332,7 +1332,7 @@ pub(crate) fn prune_repository( ErrorKind::Command, "Pack got no decision what to do with it, please report this!", ) - .add_context("pack id", pack.id.to_string())); + .attach_context("pack id", pack.id.to_string())); } PackToDo::Keep => { // keep pack: add to new index diff --git a/crates/core/src/commands/restore.rs b/crates/core/src/commands/restore.rs index a87f82d8..68defcbd 100644 --- a/crates/core/src/commands/restore.rs +++ b/crates/core/src/commands/restore.rs @@ -225,12 +225,12 @@ pub(crate) fn collect_and_prepare( if !dry_run { dest.create_dir(path) .map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Io, "Failed to create the directory. Please check the path and try again.", + err ) - .add_context("path", path.display().to_string()) - .source(err.into()) + .attach_context("path", path.display().to_string()) })?; } } @@ -453,12 +453,12 @@ fn restore_contents( if *size == 0 { let path = &filenames[i]; dest.set_length(path, *size).map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Io, "Failed to set the length of the file. Please check the path and try again.", + err, ) - .add_context("path", path.display().to_string()) - .source(err.into()) + .attach_context("path", path.display().to_string()) })?; } } @@ -506,12 +506,12 @@ fn restore_contents( .num_threads(threads) .build() .map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Multithreading, "Failed to create the thread pool. Please try again.", + err, ) - .add_context("num threads", threads.to_string()) - .source(err.into()) + .attach_context("num threads", threads.to_string()) })?; pool.in_place_scope(|s| { @@ -721,12 +721,12 @@ impl RestorePlan { let length = bl.data_length(); let usize_length = usize::try_from(length).map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Conversion, "Failed to convert the length to usize. Please try again.", + err, ) - .add_context("length", length.to_string()) - .source(err.into()) + .attach_context("length", length.to_string()) })?; let matches = open_file diff --git a/crates/core/src/crypto/aespoly1305.rs b/crates/core/src/crypto/aespoly1305.rs index 76956d2c..75d501ef 100644 --- a/crates/core/src/crypto/aespoly1305.rs +++ b/crates/core/src/crypto/aespoly1305.rs @@ -94,13 +94,13 @@ impl CryptoKey for Key { Aes256CtrPoly1305Aes::new(&self.0) .decrypt(nonce, &data[16..]) .map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Cryptography, "Data decryption failed, MAC check failed.", + err, ) - .add_context("nonce", format!("{nonce:?}")) - .source(err.into()) - .code("C001".into()) + .attach_context("nonce", format!("{nonce:?}")) + .attach_error_code("C001".into()) }) } @@ -123,9 +123,8 @@ impl CryptoKey for Key { let tag = Aes256CtrPoly1305Aes::new(&self.0) .encrypt_in_place_detached(&nonce, &[], &mut res[16..]) .map_err(|err| { - RusticError::new(ErrorKind::Cryptography, "Data encryption failed.") - .add_context("nonce", format!("{nonce:?}")) - .source(err.into()) + RusticError::with_source(ErrorKind::Cryptography, "Data encryption failed.", err) + .attach_context("nonce", format!("{nonce:?}")) })?; res.extend_from_slice(&tag); Ok(res) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index e9cdbcdf..b84b6cab 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -5,7 +5,7 @@ // use std::error::Error as StdError; // use std::fmt; -use derive_setters::Setters; +use ::std::convert::Into; use smol_str::SmolStr; use std::{ backtrace::Backtrace, @@ -18,10 +18,9 @@ pub(crate) mod constants { } /// Result type that is being returned from methods that can fail and thus have [`RusticError`]s. -pub type RusticResult = Result; +pub type RusticResult> = Result; -#[derive(thiserror::Error, Debug, Setters)] -#[setters(strip_option)] +#[derive(thiserror::Error, Debug)] #[non_exhaustive] /// Errors that can result from rustic. pub struct RusticError { @@ -41,7 +40,7 @@ pub struct RusticError { docs_url: Option, /// Error code. - code: Option, + error_code: Option, /// The URL of the issue tracker for opening a new issue. new_issue_url: Option, @@ -56,6 +55,8 @@ pub struct RusticError { status: Option, /// Backtrace of the error. + /// + // Need to use option, otherwise thiserror will not be able to derive the Error trait. backtrace: Option, } @@ -90,7 +91,7 @@ impl Display for RusticError { write!(f, "\n\nStatus: {status:?}")?; } - if let Some(code) = &self.code { + if let Some(code) = &self.error_code { let default_docs_url = SmolStr::from(constants::DEFAULT_DOCS_URL); let docs_url = self.docs_url.as_ref().unwrap_or(&default_docs_url); @@ -120,13 +121,13 @@ impl Display for RusticError { // Accessors for anything we do want to expose publicly. impl RusticError { /// Creates a new error with the given kind and guidance. - pub fn new(kind: ErrorKind, guidance: impl Into) -> Self { - Self { + pub fn new(kind: ErrorKind, guidance: impl Into) -> Box { + Box::new(Self { kind, guidance: guidance.into().into(), context: Box::default(), source: None, - code: None, + error_code: None, docs_url: None, new_issue_url: None, existing_issue_url: None, @@ -135,12 +136,37 @@ impl RusticError { // `Backtrace::capture()` will check if backtrace has been enabled // internally. It's zero cost if backtrace is disabled. backtrace: Some(Backtrace::capture()), - } + }) + } + + /// Creates a new error with the given kind and guidance. + pub fn with_source( + kind: ErrorKind, + guidance: impl Into, + source: impl Into>, + ) -> Box { + Box::new(Self { + kind, + guidance: guidance.into().into(), + context: Box::default(), + source: Some(source.into()), + error_code: None, + docs_url: None, + new_issue_url: None, + existing_issue_url: None, + severity: None, + status: None, + // `Backtrace::capture()` will check if backtrace has been enabled + // internally. It's zero cost if backtrace is disabled. + backtrace: Some(Backtrace::capture()), + }) } /// Checks if the error has a specific error code. pub fn is_code(&self, code: &str) -> bool { - self.code.as_ref().map_or(false, |c| c.as_str() == code) + self.error_code + .as_ref() + .map_or(false, |c| c.as_str() == code) } /// Expose the inner error kind. @@ -159,13 +185,13 @@ impl RusticError { pub fn from( error: T, kind: ErrorKind, - ) -> Self { - Self { + ) -> Box { + Box::new(Self { kind, guidance: error.to_string().into(), context: Box::default(), source: Some(Box::new(error)), - code: None, + error_code: None, docs_url: None, new_issue_url: None, existing_issue_url: None, @@ -174,16 +200,124 @@ impl RusticError { // `Backtrace::capture()` will check if backtrace has been enabled // internally. It's zero cost if backtrace is disabled. backtrace: Some(Backtrace::capture()), - } + }) + } +} + +// Setters for anything we do want to expose publicly. +// +// These were initially generated by `derive_setters`, +// and then manually adjusted to return `Box` instead of `Self` which +// unfortunately is not possible with the current version of the `derive_setters`. +// +// BEWARE! `attach_context` is manually implemented to allow for multiple contexts +// to be added and is not generated by `derive_setters`. +impl RusticError { + /// Attach what kind the error is. + pub fn attach_kind(self, value: impl Into) -> Box { + Box::new(RusticError { + kind: value.into(), + ..self + }) } - /// Adds a context to the error. - #[must_use] - pub fn add_context(mut self, key: &'static str, value: impl Into) -> Self { + /// Attach a chain to the cause of the error. + pub fn attach_source( + self, + value: impl Into>, + ) -> Box { + Box::new(RusticError { + source: Some(value.into()), + ..self + }) + } + + /// Attach the error message with guidance. + pub fn attach_guidance(self, value: impl Into) -> Box { + Box::new(RusticError { + guidance: value.into(), + ..self + }) + } + + // IMPORTANT: This is manually implemented to allow for multiple contexts to be added. + /// Attach context to the error. + pub fn attach_context(mut self, key: &'static str, value: impl Into) -> Box { let mut context = self.context.to_vec(); context.push((key, value.into().into())); self.context = context.into_boxed_slice(); - self + Box::new(self) + } + + /// Overwrite context of the error. + /// + /// # Caution + /// + /// This should not be used in most cases, as it will overwrite any existing contexts. + /// Rather use `attach_context` for multiple contexts. + pub fn overwrite_context(self, value: impl Into>) -> Box { + Box::new(RusticError { + context: value.into(), + ..self + }) + } + + /// Attach the URL of the documentation for the error. + pub fn attach_docs_url(self, value: impl Into) -> Box { + Box::new(RusticError { + docs_url: Some(value.into()), + ..self + }) + } + + /// Attach an error code. + pub fn attach_error_code(self, value: impl Into) -> Box { + Box::new(RusticError { + error_code: Some(value.into()), + ..self + }) + } + + /// Attach the URL of the issue tracker for opening a new issue. + pub fn attach_new_issue_url(self, value: impl Into) -> Box { + Box::new(RusticError { + new_issue_url: Some(value.into()), + ..self + }) + } + + /// Attach the URL of an already existing issue that is related to this error. + pub fn attach_existing_issue_url(self, value: impl Into) -> Box { + Box::new(RusticError { + existing_issue_url: Some(value.into()), + ..self + }) + } + + /// Attach the severity of the error. + pub fn attach_severity(self, value: impl Into) -> Box { + Box::new(RusticError { + severity: Some(value.into()), + ..self + }) + } + + /// Attach the status of the error. + pub fn attach_status(self, value: impl Into) -> Box { + Box::new(RusticError { + status: Some(value.into()), + ..self + }) + } + + /// Attach a backtrace of the error. + /// + /// This should not be used in most cases, as the backtrace is automatically captured. + pub fn attach_backtrace_manually(self, value: impl Into) -> Box { + Box::new(RusticError { + backtrace: Some(value.into()), + ..self + }) } } @@ -260,8 +394,10 @@ pub enum ErrorKind { Processing, /// Something is not supported Unsupported, - /// External Command + /// External Command Error ExternalCommand, + /// Virtual File System Error + Vfs, // /// The repository password is incorrect. Please try again. // IncorrectRepositoryPassword, // /// No repository given. Please use the --repository option. diff --git a/crates/core/src/id.rs b/crates/core/src/id.rs index 2dee6601..11c7ad26 100644 --- a/crates/core/src/id.rs +++ b/crates/core/src/id.rs @@ -82,13 +82,16 @@ pub struct Id( ); impl FromStr for Id { - type Err = RusticError; + type Err = Box; fn from_str(s: &str) -> Result { let mut id = Self::default(); hex::decode_to_slice(s, &mut id.0).map_err(|err| { - RusticError::new(ErrorKind::Parsing, - format!("Failed to decode hex string into Id. The string must be a valid hexadecimal string: {s}") - ).source(err.into()) + RusticError::with_source( + ErrorKind::Parsing, + "Failed to decode hex string into Id. The value must be a valid hexadecimal string.", + err + ) + .attach_context("value", s) })?; Ok(id) diff --git a/crates/core/src/index.rs b/crates/core/src/index.rs index 0156190b..5fd9eff8 100644 --- a/crates/core/src/index.rs +++ b/crates/core/src/index.rs @@ -206,8 +206,8 @@ pub trait ReadIndex { || { Err( RusticError::new(ErrorKind::Index, "Blob not found in index") - .add_context("blob id", id.to_string()) - .add_context("blob type", tpe.to_string()), + .attach_context("blob id", id.to_string()) + .attach_context("blob type", tpe.to_string()), ) }, |ie| ie.read_data(be), diff --git a/crates/core/src/repofile/keyfile.rs b/crates/core/src/repofile/keyfile.rs index 7e001093..f0964b06 100644 --- a/crates/core/src/repofile/keyfile.rs +++ b/crates/core/src/repofile/keyfile.rs @@ -151,11 +151,11 @@ impl KeyFile { let key = serde_json::from_slice::(&dec_data) .map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Key, "Deserializing master key from slice failed. Please check the key file.", + err, ) - .source(err.into()) })? .key(); diff --git a/crates/core/src/repository.rs b/crates/core/src/repository.rs index 24e4e4ad..2b2a1038 100644 --- a/crates/core/src/repository.rs +++ b/crates/core/src/repository.rs @@ -189,12 +189,12 @@ impl RepositoryOptions { (Some(pwd), _, _) => Ok(Some(pwd.clone())), (_, Some(file), _) => { let mut file = BufReader::new(File::open(file).map_err(|err| { - RusticError::new( + RusticError::with_source( ErrorKind::Password, "Opening password file failed. Is the path correct?", + err, ) - .add_context("path", file.display().to_string()) - .source(err.into()) + .attach_context("path", file.display().to_string()) })?); Ok(Some(read_password_from_reader(&mut file)?)) } @@ -209,12 +209,12 @@ impl RepositoryOptions { Ok(process) => process, Err(err) => { error!("password-command could not be executed: {}", err); - return Err(RusticError::new( + return Err(RusticError::with_source( ErrorKind::Password, "Password command could not be executed.", + err, ) - .add_context("command", command.to_string()) - .source(err.into())); + .attach_context("command", command.to_string())); } }; @@ -222,12 +222,12 @@ impl RepositoryOptions { Ok(output) => output, Err(err) => { error!("error reading output from password-command: {}", err); - return Err(RusticError::new( + return Err(RusticError::with_source( ErrorKind::Password, "Error reading output from password command.", + err, ) - .add_context("command", command.to_string()) - .source(err.into())); + .attach_context("command", command.to_string())); } }; @@ -242,8 +242,8 @@ impl RepositoryOptions { ErrorKind::Password, "Password command did not exit successfully.", ) - .add_context("command", command.to_string()) - .add_context("status", s)); + .attach_context("command", command.to_string()) + .attach_context("status", s)); } let mut pwd = BufReader::new(&*output.stdout); @@ -265,9 +265,12 @@ impl RepositoryOptions { pub fn read_password_from_reader(file: &mut impl BufRead) -> RusticResult { let mut password = String::new(); _ = file.read_line(&mut password).map_err(|err| { - RusticError::new(ErrorKind::Password, "Reading password from reader failed. Is the file empty? Please check the file and the password.") - .add_context("password", password.clone()) - .source(err.into()) + RusticError::with_source( + ErrorKind::Password, + "Reading password from reader failed. Is the file empty? Please check the file and the password.", + err + ) + .attach_context("password", password.clone()) })?; // Remove the \n from the line if present @@ -370,7 +373,7 @@ impl

Repository { ErrorKind::Command, "No `%id` specified in warm-up command. Please specify `%id` in the command.", ) - .add_context("command", warm_up.to_string())); + .attach_context("command", warm_up.to_string())); } info!("using warm-up command {warm_up}"); } @@ -444,7 +447,7 @@ impl Repository { ErrorKind::Config, "More than one repository found. Please check the config file.", ) - .add_context("name", self.name.clone())), + .attach_context("name", self.name.clone())), } } @@ -523,7 +526,7 @@ impl Repository { ErrorKind::Config, "No repository config file found. Please check the repository.", ) - .add_context("name", self.name.clone()), + .attach_context("name", self.name.clone()), )?; if let Some(be_hot) = &self.be_hot { @@ -536,7 +539,7 @@ impl Repository { ErrorKind::Key, "Keys of hot and cold repositories don't match. Please check the keys.", ) - .add_context("name", self.name.clone())); + .attach_context("name", self.name.clone())); } } @@ -587,7 +590,7 @@ impl Repository { ErrorKind::Password, "No password given, or Password was empty. Please specify a valid password.", ) - .add_context("name", self.name.clone()), + .attach_context("name", self.name.clone()), )?; self.init_with_password(&password, key_opts, config_opts) @@ -627,7 +630,7 @@ impl Repository { ErrorKind::Config, "Config file already exists. Please check the repository.", ) - .add_context("name", self.name.clone())); + .attach_context("name", self.name.clone())); } let (key, config) = commands::init::init(&self, pass, key_opts, config_opts)?; @@ -1633,7 +1636,7 @@ impl Repository { ErrorKind::Index, "BlobID not found in index, but should be there. This is a bug. Please report it.", ) - .add_context("blob id", blob_id.to_string()) + .attach_context("blob id", blob_id.to_string()) })?; Ok(ie) diff --git a/crates/core/src/vfs.rs b/crates/core/src/vfs.rs index 0ac59256..64cd9c05 100644 --- a/crates/core/src/vfs.rs +++ b/crates/core/src/vfs.rs @@ -23,6 +23,7 @@ use crate::{ repofile::{BlobType, Metadata, Node, NodeType, SnapshotFile}, repository::{IndexedFull, IndexedTree, Repository}, vfs::format::FormattedSnapshot, + ErrorKind, RusticError, }; /// [`VfsErrorKind`] describes the errors that can be returned from the Virtual File System @@ -276,12 +277,26 @@ impl Vfs { && last_tree == snap.tree { if let Some(name) = last_name { - tree.add_tree(path, VfsTree::Link(name)) - .map_err(|_err| todo!("Error transition"))?; + tree.add_tree(path, VfsTree::Link(name.clone())).map_err(|err| { + RusticError::with_source( + ErrorKind::Vfs, + "Failed to add a link to root tree. This should not happen, please report this bug.", + err + ) + .attach_context("path", path.display().to_string()) + .attach_context("name", name.to_string_lossy()) + })?; } } else { - tree.add_tree(path, VfsTree::RusticTree(snap.tree)) - .map_err(|_err| todo!("Error transition"))?; + tree.add_tree(path, VfsTree::RusticTree(snap.tree)).map_err(|err| { + RusticError::with_source( + ErrorKind::Vfs, + "Failed to add repository tree to root tree. This should not happen, please report this bug.", + err + ) + .attach_context("path", path.display().to_string()) + .attach_context("tree id", snap.tree.to_string()) + })?; } } last_parent = parent_path; @@ -296,8 +311,17 @@ impl Vfs { for (path, target) in dirs_for_link { if let (Some(mut path), Some(target)) = (path, target) { path.push("latest"); - tree.add_tree(&path, VfsTree::Link(target)) - .map_err(|_err| todo!("Error transition"))?; + tree.add_tree(&path, VfsTree::Link(target.clone())) + .map_err(|err| { + RusticError::with_source( + ErrorKind::Vfs, + "Failed to link latest entries to root tree. This should not happen, please report this bug.", + err + ) + .attach_context("latest", "link") + .attach_context("path", path.display().to_string()) + .attach_context("target", target.to_string_lossy()) + })?; } } } @@ -306,7 +330,16 @@ impl Vfs { if let Some(mut path) = path { path.push("latest"); tree.add_tree(&path, VfsTree::RusticTree(subtree)) - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| { + RusticError::with_source( + ErrorKind::Vfs, + "Failed to add latest subtree to root tree. This should not happen, please report this bug.", + err + ) + .attach_context("latest", "dir") + .attach_context("path", path.display().to_string()) + .attach_context("tree id", subtree.to_string()) + })?; } } } @@ -337,11 +370,14 @@ impl Vfs { path: &Path, ) -> RusticResult { let meta = Metadata::default(); - match self - .tree - .get_path(path) - .map_err(|_err| todo!("Error transition"))? - { + match self.tree.get_path(path).map_err(|err| { + RusticError::with_source( + ErrorKind::Vfs, + "Failed to get tree at given path. This should not happen, please report this bug.", + err, + ) + .attach_context("path", path.display().to_string()) + })? { VfsPath::RusticPath(tree_id, path) => Ok(repo.node_from_path(*tree_id, &path)?), VfsPath::VirtualTree(_) => { Ok(Node::new(String::new(), NodeType::Dir, meta, None, None)) @@ -384,11 +420,14 @@ impl Vfs { repo: &Repository, path: &Path, ) -> RusticResult> { - let result = match self - .tree - .get_path(path) - .map_err(|_err| todo!("Error transition"))? - { + let result = match self.tree.get_path(path).map_err(|err| { + RusticError::with_source( + ErrorKind::Vfs, + "Failed to get tree at given path. This should not happen, please report this bug.", + err, + ) + .attach_context("path", path.display().to_string()) + })? { VfsPath::RusticPath(tree_id, path) => { let node = repo.node_from_path(*tree_id, &path)?; if node.is_dir() { diff --git a/crates/core/tests/errors.rs b/crates/core/tests/errors.rs index 8dc2d35e..e785341c 100644 --- a/crates/core/tests/errors.rs +++ b/crates/core/tests/errors.rs @@ -9,9 +9,9 @@ fn error() -> RusticError { ErrorKind::Io, "A file could not be read, make sure the file is existing and readable by the system.", ) - .status(Status::Permanent) - .severity(Severity::Error) - .code("E001".into()) + .attach_status(Status::Permanent) + .attach_severity(Severity::Error) + .attach_error_code("E001".into()) .add_context("path", "/path/to/file") .add_context("called", "used s3 backend") .source(std::io::Error::new(std::io::ErrorKind::Other, "networking error").into()) diff --git a/crates/testing/src/backend.rs b/crates/testing/src/backend.rs index 51f6996a..26dbc4fd 100644 --- a/crates/testing/src/backend.rs +++ b/crates/testing/src/backend.rs @@ -74,7 +74,7 @@ pub mod in_memory_backend { ) -> RusticResult<()> { if self.0.write().unwrap()[tpe].insert(*id, buf).is_some() { return Err(RusticError::new(ErrorKind::Backend, "Id already exists.") - .add_context("id", id.to_string())); + .attach_context("id", id.to_string())); } Ok(()) @@ -83,7 +83,7 @@ pub mod in_memory_backend { fn remove(&self, tpe: FileType, id: &Id, _cacheable: bool) -> RusticResult<()> { if self.0.write().unwrap()[tpe].remove(id).is_none() { return Err(RusticError::new(ErrorKind::Backend, "Id does not exist.") - .add_context("id", id.to_string())); + .attach_context("id", id.to_string())); } Ok(()) } diff --git a/error_expanded.rs b/error_expanded.rs new file mode 100644 index 00000000..ada21351 --- /dev/null +++ b/error_expanded.rs @@ -0,0 +1,560 @@ +pub(crate) mod error { + //! Error types and Result module. + #![allow(clippy::doc_markdown)] + use derive_setters::Setters; + use smol_str::SmolStr; + use std::{backtrace::Backtrace, fmt::{self, Display}}; + pub(crate) mod constants { + pub const DEFAULT_DOCS_URL: &str = "https://rustic.cli.rs/docs/errors/"; + pub const DEFAULT_ISSUE_URL: &str = "https://github.com/rustic-rs/rustic_core/issues/new"; + } + /// Result type that is being returned from methods that can fail and thus have [`RusticError`]s. + pub type RusticResult> = Result; + #[setters(strip_option, into)] + #[non_exhaustive] + /// Errors that can result from rustic. + pub struct RusticError { + /// The kind of the error. + kind: ErrorKind, + /// Chain to the cause of the error. + source: Option>, + /// The error message with guidance. + guidance: SmolStr, + /// The context of the error. + context: Box<[(&'static str, SmolStr)]>, + /// The URL of the documentation for the error. + docs_url: Option, + /// Error code. + code: Option, + /// The URL of the issue tracker for opening a new issue. + new_issue_url: Option, + /// The URL of an already existing issue that is related to this error. + existing_issue_url: Option, + /// Severity of the error. + severity: Option, + /// The status of the error. + status: Option, + /// Backtrace of the error. + backtrace: Option, + } + #[allow(unused_qualifications)] + #[automatically_derived] + impl std::error::Error for RusticError { + fn source(&self) -> ::core::option::Option<&(dyn std::error::Error + 'static)> { + use thiserror::__private::AsDynError as _; + ::core::option::Option::Some(self.source.as_ref()?.as_dyn_error()) + } + } + #[automatically_derived] + impl ::core::fmt::Debug for RusticError { + #[inline] + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + let names: &'static _ = &[ + "kind", + "source", + "guidance", + "context", + "docs_url", + "code", + "new_issue_url", + "existing_issue_url", + "severity", + "status", + "backtrace", + ]; + let values: &[&dyn ::core::fmt::Debug] = &[ + &self.kind, + &self.source, + &self.guidance, + &self.context, + &self.docs_url, + &self.code, + &self.new_issue_url, + &self.existing_issue_url, + &self.severity, + &self.status, + &&self.backtrace, + ]; + ::core::fmt::Formatter::debug_struct_fields_finish( + f, + "RusticError", + names, + values, + ) + } + } + impl RusticError { + /// The kind of the error. + pub fn kind(self, value: impl ::std::convert::Into) -> Self { + RusticError { + kind: value.into(), + ..self + } + } + /// Chain to the cause of the error. + pub fn source( + self, + value: impl ::std::convert::Into>, + ) -> Self { + RusticError { + source: Some(value.into()), + ..self + } + } + /// The error message with guidance. + pub fn guidance(self, value: impl ::std::convert::Into) -> Self { + RusticError { + guidance: value.into(), + ..self + } + } + /// The context of the error. + pub fn context( + self, + value: impl ::std::convert::Into>, + ) -> Self { + RusticError { + context: value.into(), + ..self + } + } + /// The URL of the documentation for the error. + pub fn docs_url(self, value: impl ::std::convert::Into) -> Self { + RusticError { + docs_url: Some(value.into()), + ..self + } + } + /// Error code. + pub fn code(self, value: impl ::std::convert::Into) -> Self { + RusticError { + code: Some(value.into()), + ..self + } + } + /// The URL of the issue tracker for opening a new issue. + pub fn new_issue_url(self, value: impl ::std::convert::Into) -> Self { + RusticError { + new_issue_url: Some(value.into()), + ..self + } + } + /// The URL of an already existing issue that is related to this error. + pub fn existing_issue_url( + self, + value: impl ::std::convert::Into, + ) -> Self { + RusticError { + existing_issue_url: Some(value.into()), + ..self + } + } + /// Severity of the error. + pub fn severity(self, value: impl ::std::convert::Into) -> Self { + RusticError { + severity: Some(value.into()), + ..self + } + } + /// The status of the error. + pub fn status(self, value: impl ::std::convert::Into) -> Self { + RusticError { + status: Some(value.into()), + ..self + } + } + /// Backtrace of the error. + pub fn backtrace(self, value: impl ::std::convert::Into) -> Self { + RusticError { + backtrace: Some(value.into()), + ..self + } + } + } + impl Display for RusticError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!("{0} occurred in `rustic_core`", self.kind))?; + f.write_fmt(format_args!("\n\nMessage:\n{0}", self.guidance))?; + if !self.context.is_empty() { + f.write_fmt(format_args!("\n\nContext:\n"))?; + f.write_fmt( + format_args!( + "{0}", + self + .context + .iter() + .map(|(k, v)| ::alloc::__export::must_use({ + let res = ::alloc::fmt::format( + format_args!("{0}: {1}", k, v), + ); + res + })) + .collect::>() + .join(",\n"), + ), + )?; + } + if let Some(cause) = &self.source { + f.write_fmt(format_args!("\n\nCaused by: {0}", cause))?; + } + if let Some(severity) = &self.severity { + f.write_fmt(format_args!("\n\nSeverity: {0:?}", severity))?; + } + if let Some(status) = &self.status { + f.write_fmt(format_args!("\n\nStatus: {0:?}", status))?; + } + if let Some(code) = &self.code { + let default_docs_url = SmolStr::from(constants::DEFAULT_DOCS_URL); + let docs_url = self.docs_url.as_ref().unwrap_or(&default_docs_url); + f.write_fmt( + format_args!("\n\nFor more information, see: {0}{1}", docs_url, code), + )?; + } + if let Some(existing_issue_url) = &self.existing_issue_url { + f.write_fmt( + format_args!( + "\n\nThis might be a related issue, please check it for a possible workaround and/or further guidance: {0}", + existing_issue_url, + ), + )?; + } + let default_issue_url = SmolStr::from(constants::DEFAULT_ISSUE_URL); + let new_issue_url = self + .new_issue_url + .as_ref() + .unwrap_or(&default_issue_url); + f.write_fmt( + format_args!( + "\n\nIf you think this is an undiscovered bug, please open an issue at: {0}", + new_issue_url, + ), + )?; + if let Some(backtrace) = &self.backtrace { + f.write_fmt(format_args!("\n\nBacktrace:\n{0:?}", backtrace))?; + } + Ok(()) + } + } + impl RusticError { + /// Creates a new error with the given kind and guidance. + pub fn new(kind: ErrorKind, guidance: impl Into) -> Box { + Box::new(Self { + kind, + guidance: guidance.into().into(), + context: Box::default(), + source: None, + code: None, + docs_url: None, + new_issue_url: None, + existing_issue_url: None, + severity: None, + status: None, + backtrace: Some(Backtrace::capture()), + }) + } + /// Checks if the error has a specific error code. + pub fn is_code(&self, code: &str) -> bool { + self.code.as_ref().map_or(false, |c| c.as_str() == code) + } + /// Expose the inner error kind. + /// + /// This is useful for matching on the error kind. + pub fn into_inner(self) -> ErrorKind { + self.kind + } + /// Checks if the error is due to an incorrect password + pub fn is_incorrect_password(&self) -> bool { + match self.kind { + ErrorKind::Password => true, + _ => false, + } + } + /// Creates a new error from a given error. + pub fn from( + error: T, + kind: ErrorKind, + ) -> Box { + Box::new(Self { + kind, + guidance: error.to_string().into(), + context: Box::default(), + source: Some(Box::new(error)), + code: None, + docs_url: None, + new_issue_url: None, + existing_issue_url: None, + severity: None, + status: None, + backtrace: Some(Backtrace::capture()), + }) + } + /// Adds a context to the error. + #[must_use] + pub fn with_context( + mut self, + key: &'static str, + value: impl Into, + ) -> Box { + let mut context = self.context.to_vec(); + context.push((key, value.into().into())); + self.context = context.into_boxed_slice(); + Box::new(self) + } + } + /// Severity of an error, ranging from informational to fatal. + pub enum Severity { + /// Informational + Info, + /// Warning + Warning, + /// Error + Error, + /// Fatal + Fatal, + } + #[automatically_derived] + impl ::core::fmt::Debug for Severity { + #[inline] + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + ::core::fmt::Formatter::write_str( + f, + match self { + Severity::Info => "Info", + Severity::Warning => "Warning", + Severity::Error => "Error", + Severity::Fatal => "Fatal", + }, + ) + } + } + #[automatically_derived] + impl ::core::clone::Clone for Severity { + #[inline] + fn clone(&self) -> Severity { + *self + } + } + #[automatically_derived] + impl ::core::marker::Copy for Severity {} + #[automatically_derived] + impl ::core::marker::StructuralPartialEq for Severity {} + #[automatically_derived] + impl ::core::cmp::PartialEq for Severity { + #[inline] + fn eq(&self, other: &Severity) -> bool { + let __self_discr = ::core::intrinsics::discriminant_value(self); + let __arg1_discr = ::core::intrinsics::discriminant_value(other); + __self_discr == __arg1_discr + } + } + #[automatically_derived] + impl ::core::cmp::Eq for Severity { + #[inline] + #[doc(hidden)] + #[coverage(off)] + fn assert_receiver_is_total_eq(&self) -> () {} + } + /// Status of an error, indicating whether it is permanent, temporary, or persistent. + pub enum Status { + /// Permanent, may not be retried + Permanent, + /// Temporary, may be retried + Temporary, + /// Persistent, may be retried, but may not succeed + Persistent, + } + #[automatically_derived] + impl ::core::fmt::Debug for Status { + #[inline] + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + ::core::fmt::Formatter::write_str( + f, + match self { + Status::Permanent => "Permanent", + Status::Temporary => "Temporary", + Status::Persistent => "Persistent", + }, + ) + } + } + #[automatically_derived] + impl ::core::clone::Clone for Status { + #[inline] + fn clone(&self) -> Status { + *self + } + } + #[automatically_derived] + impl ::core::marker::Copy for Status {} + #[automatically_derived] + impl ::core::marker::StructuralPartialEq for Status {} + #[automatically_derived] + impl ::core::cmp::PartialEq for Status { + #[inline] + fn eq(&self, other: &Status) -> bool { + let __self_discr = ::core::intrinsics::discriminant_value(self); + let __arg1_discr = ::core::intrinsics::discriminant_value(other); + __self_discr == __arg1_discr + } + } + #[automatically_derived] + impl ::core::cmp::Eq for Status { + #[inline] + #[doc(hidden)] + #[coverage(off)] + fn assert_receiver_is_total_eq(&self) -> () {} + } + /// [`ErrorKind`] describes the errors that can happen while executing a high-level command. + /// + /// This is a non-exhaustive enum, so additional variants may be added in future. It is + /// recommended to match against the wildcard `_` instead of listing all possible variants, + /// to avoid problems when new variants are added. + #[non_exhaustive] + pub enum ErrorKind { + /// Backend Error + Backend, + /// IO Error + Io, + /// Password Error + Password, + /// Repository Error + Repository, + /// Command Error + Command, + /// Config Error + Config, + /// Index Error + Index, + /// Key Error + Key, + /// Blob Error + Blob, + /// Crypto Error + Cryptography, + /// Compression Error + Compression, + /// Parsing Error + Parsing, + /// Conversion Error + Conversion, + /// Permission Error + Permission, + /// Polynomial Error + Polynomial, + /// Multithreading Error + Multithreading, + /// Processing Error + Processing, + /// Something is not supported + Unsupported, + /// External Command + ExternalCommand, + } + #[allow(unused_qualifications)] + #[automatically_derived] + impl std::error::Error for ErrorKind {} + #[automatically_derived] + impl ::core::fmt::Debug for ErrorKind { + #[inline] + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + ::core::fmt::Formatter::write_str( + f, + match self { + ErrorKind::Backend => "Backend", + ErrorKind::Io => "Io", + ErrorKind::Password => "Password", + ErrorKind::Repository => "Repository", + ErrorKind::Command => "Command", + ErrorKind::Config => "Config", + ErrorKind::Index => "Index", + ErrorKind::Key => "Key", + ErrorKind::Blob => "Blob", + ErrorKind::Cryptography => "Cryptography", + ErrorKind::Compression => "Compression", + ErrorKind::Parsing => "Parsing", + ErrorKind::Conversion => "Conversion", + ErrorKind::Permission => "Permission", + ErrorKind::Polynomial => "Polynomial", + ErrorKind::Multithreading => "Multithreading", + ErrorKind::Processing => "Processing", + ErrorKind::Unsupported => "Unsupported", + ErrorKind::ExternalCommand => "ExternalCommand", + }, + ) + } + } + #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)] + const _: () = { + trait DisplayToDisplayDoc { + fn __displaydoc_display(&self) -> Self; + } + impl DisplayToDisplayDoc for &T { + fn __displaydoc_display(&self) -> Self { + self + } + } + extern crate std; + trait PathToDisplayDoc { + fn __displaydoc_display(&self) -> std::path::Display<'_>; + } + impl PathToDisplayDoc for std::path::Path { + fn __displaydoc_display(&self) -> std::path::Display<'_> { + self.display() + } + } + impl PathToDisplayDoc for std::path::PathBuf { + fn __displaydoc_display(&self) -> std::path::Display<'_> { + self.display() + } + } + impl ::core::fmt::Display for ErrorKind { + fn fmt( + &self, + formatter: &mut ::core::fmt::Formatter, + ) -> ::core::fmt::Result { + #[allow(unused_variables)] + match self { + Self::Backend => formatter.write_fmt(format_args!("Backend Error")), + Self::Io => formatter.write_fmt(format_args!("IO Error")), + Self::Password => formatter.write_fmt(format_args!("Password Error")), + Self::Repository => { + formatter.write_fmt(format_args!("Repository Error")) + } + Self::Command => formatter.write_fmt(format_args!("Command Error")), + Self::Config => formatter.write_fmt(format_args!("Config Error")), + Self::Index => formatter.write_fmt(format_args!("Index Error")), + Self::Key => formatter.write_fmt(format_args!("Key Error")), + Self::Blob => formatter.write_fmt(format_args!("Blob Error")), + Self::Cryptography => { + formatter.write_fmt(format_args!("Crypto Error")) + } + Self::Compression => { + formatter.write_fmt(format_args!("Compression Error")) + } + Self::Parsing => formatter.write_fmt(format_args!("Parsing Error")), + Self::Conversion => { + formatter.write_fmt(format_args!("Conversion Error")) + } + Self::Permission => { + formatter.write_fmt(format_args!("Permission Error")) + } + Self::Polynomial => { + formatter.write_fmt(format_args!("Polynomial Error")) + } + Self::Multithreading => { + formatter.write_fmt(format_args!("Multithreading Error")) + } + Self::Processing => { + formatter.write_fmt(format_args!("Processing Error")) + } + Self::Unsupported => { + formatter.write_fmt(format_args!("Something is not supported")) + } + Self::ExternalCommand => { + formatter.write_fmt(format_args!("External Command")) + } + } + } + } + }; +} From 6a540f0ffdbb77537dac3258e7eb871a21d0475c Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sat, 26 Oct 2024 17:37:53 +0200 Subject: [PATCH 038/129] remove wrongly commited macro expanded file Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- error_expanded.rs | 560 ---------------------------------------------- 1 file changed, 560 deletions(-) delete mode 100644 error_expanded.rs diff --git a/error_expanded.rs b/error_expanded.rs deleted file mode 100644 index ada21351..00000000 --- a/error_expanded.rs +++ /dev/null @@ -1,560 +0,0 @@ -pub(crate) mod error { - //! Error types and Result module. - #![allow(clippy::doc_markdown)] - use derive_setters::Setters; - use smol_str::SmolStr; - use std::{backtrace::Backtrace, fmt::{self, Display}}; - pub(crate) mod constants { - pub const DEFAULT_DOCS_URL: &str = "https://rustic.cli.rs/docs/errors/"; - pub const DEFAULT_ISSUE_URL: &str = "https://github.com/rustic-rs/rustic_core/issues/new"; - } - /// Result type that is being returned from methods that can fail and thus have [`RusticError`]s. - pub type RusticResult> = Result; - #[setters(strip_option, into)] - #[non_exhaustive] - /// Errors that can result from rustic. - pub struct RusticError { - /// The kind of the error. - kind: ErrorKind, - /// Chain to the cause of the error. - source: Option>, - /// The error message with guidance. - guidance: SmolStr, - /// The context of the error. - context: Box<[(&'static str, SmolStr)]>, - /// The URL of the documentation for the error. - docs_url: Option, - /// Error code. - code: Option, - /// The URL of the issue tracker for opening a new issue. - new_issue_url: Option, - /// The URL of an already existing issue that is related to this error. - existing_issue_url: Option, - /// Severity of the error. - severity: Option, - /// The status of the error. - status: Option, - /// Backtrace of the error. - backtrace: Option, - } - #[allow(unused_qualifications)] - #[automatically_derived] - impl std::error::Error for RusticError { - fn source(&self) -> ::core::option::Option<&(dyn std::error::Error + 'static)> { - use thiserror::__private::AsDynError as _; - ::core::option::Option::Some(self.source.as_ref()?.as_dyn_error()) - } - } - #[automatically_derived] - impl ::core::fmt::Debug for RusticError { - #[inline] - fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - let names: &'static _ = &[ - "kind", - "source", - "guidance", - "context", - "docs_url", - "code", - "new_issue_url", - "existing_issue_url", - "severity", - "status", - "backtrace", - ]; - let values: &[&dyn ::core::fmt::Debug] = &[ - &self.kind, - &self.source, - &self.guidance, - &self.context, - &self.docs_url, - &self.code, - &self.new_issue_url, - &self.existing_issue_url, - &self.severity, - &self.status, - &&self.backtrace, - ]; - ::core::fmt::Formatter::debug_struct_fields_finish( - f, - "RusticError", - names, - values, - ) - } - } - impl RusticError { - /// The kind of the error. - pub fn kind(self, value: impl ::std::convert::Into) -> Self { - RusticError { - kind: value.into(), - ..self - } - } - /// Chain to the cause of the error. - pub fn source( - self, - value: impl ::std::convert::Into>, - ) -> Self { - RusticError { - source: Some(value.into()), - ..self - } - } - /// The error message with guidance. - pub fn guidance(self, value: impl ::std::convert::Into) -> Self { - RusticError { - guidance: value.into(), - ..self - } - } - /// The context of the error. - pub fn context( - self, - value: impl ::std::convert::Into>, - ) -> Self { - RusticError { - context: value.into(), - ..self - } - } - /// The URL of the documentation for the error. - pub fn docs_url(self, value: impl ::std::convert::Into) -> Self { - RusticError { - docs_url: Some(value.into()), - ..self - } - } - /// Error code. - pub fn code(self, value: impl ::std::convert::Into) -> Self { - RusticError { - code: Some(value.into()), - ..self - } - } - /// The URL of the issue tracker for opening a new issue. - pub fn new_issue_url(self, value: impl ::std::convert::Into) -> Self { - RusticError { - new_issue_url: Some(value.into()), - ..self - } - } - /// The URL of an already existing issue that is related to this error. - pub fn existing_issue_url( - self, - value: impl ::std::convert::Into, - ) -> Self { - RusticError { - existing_issue_url: Some(value.into()), - ..self - } - } - /// Severity of the error. - pub fn severity(self, value: impl ::std::convert::Into) -> Self { - RusticError { - severity: Some(value.into()), - ..self - } - } - /// The status of the error. - pub fn status(self, value: impl ::std::convert::Into) -> Self { - RusticError { - status: Some(value.into()), - ..self - } - } - /// Backtrace of the error. - pub fn backtrace(self, value: impl ::std::convert::Into) -> Self { - RusticError { - backtrace: Some(value.into()), - ..self - } - } - } - impl Display for RusticError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_fmt(format_args!("{0} occurred in `rustic_core`", self.kind))?; - f.write_fmt(format_args!("\n\nMessage:\n{0}", self.guidance))?; - if !self.context.is_empty() { - f.write_fmt(format_args!("\n\nContext:\n"))?; - f.write_fmt( - format_args!( - "{0}", - self - .context - .iter() - .map(|(k, v)| ::alloc::__export::must_use({ - let res = ::alloc::fmt::format( - format_args!("{0}: {1}", k, v), - ); - res - })) - .collect::>() - .join(",\n"), - ), - )?; - } - if let Some(cause) = &self.source { - f.write_fmt(format_args!("\n\nCaused by: {0}", cause))?; - } - if let Some(severity) = &self.severity { - f.write_fmt(format_args!("\n\nSeverity: {0:?}", severity))?; - } - if let Some(status) = &self.status { - f.write_fmt(format_args!("\n\nStatus: {0:?}", status))?; - } - if let Some(code) = &self.code { - let default_docs_url = SmolStr::from(constants::DEFAULT_DOCS_URL); - let docs_url = self.docs_url.as_ref().unwrap_or(&default_docs_url); - f.write_fmt( - format_args!("\n\nFor more information, see: {0}{1}", docs_url, code), - )?; - } - if let Some(existing_issue_url) = &self.existing_issue_url { - f.write_fmt( - format_args!( - "\n\nThis might be a related issue, please check it for a possible workaround and/or further guidance: {0}", - existing_issue_url, - ), - )?; - } - let default_issue_url = SmolStr::from(constants::DEFAULT_ISSUE_URL); - let new_issue_url = self - .new_issue_url - .as_ref() - .unwrap_or(&default_issue_url); - f.write_fmt( - format_args!( - "\n\nIf you think this is an undiscovered bug, please open an issue at: {0}", - new_issue_url, - ), - )?; - if let Some(backtrace) = &self.backtrace { - f.write_fmt(format_args!("\n\nBacktrace:\n{0:?}", backtrace))?; - } - Ok(()) - } - } - impl RusticError { - /// Creates a new error with the given kind and guidance. - pub fn new(kind: ErrorKind, guidance: impl Into) -> Box { - Box::new(Self { - kind, - guidance: guidance.into().into(), - context: Box::default(), - source: None, - code: None, - docs_url: None, - new_issue_url: None, - existing_issue_url: None, - severity: None, - status: None, - backtrace: Some(Backtrace::capture()), - }) - } - /// Checks if the error has a specific error code. - pub fn is_code(&self, code: &str) -> bool { - self.code.as_ref().map_or(false, |c| c.as_str() == code) - } - /// Expose the inner error kind. - /// - /// This is useful for matching on the error kind. - pub fn into_inner(self) -> ErrorKind { - self.kind - } - /// Checks if the error is due to an incorrect password - pub fn is_incorrect_password(&self) -> bool { - match self.kind { - ErrorKind::Password => true, - _ => false, - } - } - /// Creates a new error from a given error. - pub fn from( - error: T, - kind: ErrorKind, - ) -> Box { - Box::new(Self { - kind, - guidance: error.to_string().into(), - context: Box::default(), - source: Some(Box::new(error)), - code: None, - docs_url: None, - new_issue_url: None, - existing_issue_url: None, - severity: None, - status: None, - backtrace: Some(Backtrace::capture()), - }) - } - /// Adds a context to the error. - #[must_use] - pub fn with_context( - mut self, - key: &'static str, - value: impl Into, - ) -> Box { - let mut context = self.context.to_vec(); - context.push((key, value.into().into())); - self.context = context.into_boxed_slice(); - Box::new(self) - } - } - /// Severity of an error, ranging from informational to fatal. - pub enum Severity { - /// Informational - Info, - /// Warning - Warning, - /// Error - Error, - /// Fatal - Fatal, - } - #[automatically_derived] - impl ::core::fmt::Debug for Severity { - #[inline] - fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - ::core::fmt::Formatter::write_str( - f, - match self { - Severity::Info => "Info", - Severity::Warning => "Warning", - Severity::Error => "Error", - Severity::Fatal => "Fatal", - }, - ) - } - } - #[automatically_derived] - impl ::core::clone::Clone for Severity { - #[inline] - fn clone(&self) -> Severity { - *self - } - } - #[automatically_derived] - impl ::core::marker::Copy for Severity {} - #[automatically_derived] - impl ::core::marker::StructuralPartialEq for Severity {} - #[automatically_derived] - impl ::core::cmp::PartialEq for Severity { - #[inline] - fn eq(&self, other: &Severity) -> bool { - let __self_discr = ::core::intrinsics::discriminant_value(self); - let __arg1_discr = ::core::intrinsics::discriminant_value(other); - __self_discr == __arg1_discr - } - } - #[automatically_derived] - impl ::core::cmp::Eq for Severity { - #[inline] - #[doc(hidden)] - #[coverage(off)] - fn assert_receiver_is_total_eq(&self) -> () {} - } - /// Status of an error, indicating whether it is permanent, temporary, or persistent. - pub enum Status { - /// Permanent, may not be retried - Permanent, - /// Temporary, may be retried - Temporary, - /// Persistent, may be retried, but may not succeed - Persistent, - } - #[automatically_derived] - impl ::core::fmt::Debug for Status { - #[inline] - fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - ::core::fmt::Formatter::write_str( - f, - match self { - Status::Permanent => "Permanent", - Status::Temporary => "Temporary", - Status::Persistent => "Persistent", - }, - ) - } - } - #[automatically_derived] - impl ::core::clone::Clone for Status { - #[inline] - fn clone(&self) -> Status { - *self - } - } - #[automatically_derived] - impl ::core::marker::Copy for Status {} - #[automatically_derived] - impl ::core::marker::StructuralPartialEq for Status {} - #[automatically_derived] - impl ::core::cmp::PartialEq for Status { - #[inline] - fn eq(&self, other: &Status) -> bool { - let __self_discr = ::core::intrinsics::discriminant_value(self); - let __arg1_discr = ::core::intrinsics::discriminant_value(other); - __self_discr == __arg1_discr - } - } - #[automatically_derived] - impl ::core::cmp::Eq for Status { - #[inline] - #[doc(hidden)] - #[coverage(off)] - fn assert_receiver_is_total_eq(&self) -> () {} - } - /// [`ErrorKind`] describes the errors that can happen while executing a high-level command. - /// - /// This is a non-exhaustive enum, so additional variants may be added in future. It is - /// recommended to match against the wildcard `_` instead of listing all possible variants, - /// to avoid problems when new variants are added. - #[non_exhaustive] - pub enum ErrorKind { - /// Backend Error - Backend, - /// IO Error - Io, - /// Password Error - Password, - /// Repository Error - Repository, - /// Command Error - Command, - /// Config Error - Config, - /// Index Error - Index, - /// Key Error - Key, - /// Blob Error - Blob, - /// Crypto Error - Cryptography, - /// Compression Error - Compression, - /// Parsing Error - Parsing, - /// Conversion Error - Conversion, - /// Permission Error - Permission, - /// Polynomial Error - Polynomial, - /// Multithreading Error - Multithreading, - /// Processing Error - Processing, - /// Something is not supported - Unsupported, - /// External Command - ExternalCommand, - } - #[allow(unused_qualifications)] - #[automatically_derived] - impl std::error::Error for ErrorKind {} - #[automatically_derived] - impl ::core::fmt::Debug for ErrorKind { - #[inline] - fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - ::core::fmt::Formatter::write_str( - f, - match self { - ErrorKind::Backend => "Backend", - ErrorKind::Io => "Io", - ErrorKind::Password => "Password", - ErrorKind::Repository => "Repository", - ErrorKind::Command => "Command", - ErrorKind::Config => "Config", - ErrorKind::Index => "Index", - ErrorKind::Key => "Key", - ErrorKind::Blob => "Blob", - ErrorKind::Cryptography => "Cryptography", - ErrorKind::Compression => "Compression", - ErrorKind::Parsing => "Parsing", - ErrorKind::Conversion => "Conversion", - ErrorKind::Permission => "Permission", - ErrorKind::Polynomial => "Polynomial", - ErrorKind::Multithreading => "Multithreading", - ErrorKind::Processing => "Processing", - ErrorKind::Unsupported => "Unsupported", - ErrorKind::ExternalCommand => "ExternalCommand", - }, - ) - } - } - #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)] - const _: () = { - trait DisplayToDisplayDoc { - fn __displaydoc_display(&self) -> Self; - } - impl DisplayToDisplayDoc for &T { - fn __displaydoc_display(&self) -> Self { - self - } - } - extern crate std; - trait PathToDisplayDoc { - fn __displaydoc_display(&self) -> std::path::Display<'_>; - } - impl PathToDisplayDoc for std::path::Path { - fn __displaydoc_display(&self) -> std::path::Display<'_> { - self.display() - } - } - impl PathToDisplayDoc for std::path::PathBuf { - fn __displaydoc_display(&self) -> std::path::Display<'_> { - self.display() - } - } - impl ::core::fmt::Display for ErrorKind { - fn fmt( - &self, - formatter: &mut ::core::fmt::Formatter, - ) -> ::core::fmt::Result { - #[allow(unused_variables)] - match self { - Self::Backend => formatter.write_fmt(format_args!("Backend Error")), - Self::Io => formatter.write_fmt(format_args!("IO Error")), - Self::Password => formatter.write_fmt(format_args!("Password Error")), - Self::Repository => { - formatter.write_fmt(format_args!("Repository Error")) - } - Self::Command => formatter.write_fmt(format_args!("Command Error")), - Self::Config => formatter.write_fmt(format_args!("Config Error")), - Self::Index => formatter.write_fmt(format_args!("Index Error")), - Self::Key => formatter.write_fmt(format_args!("Key Error")), - Self::Blob => formatter.write_fmt(format_args!("Blob Error")), - Self::Cryptography => { - formatter.write_fmt(format_args!("Crypto Error")) - } - Self::Compression => { - formatter.write_fmt(format_args!("Compression Error")) - } - Self::Parsing => formatter.write_fmt(format_args!("Parsing Error")), - Self::Conversion => { - formatter.write_fmt(format_args!("Conversion Error")) - } - Self::Permission => { - formatter.write_fmt(format_args!("Permission Error")) - } - Self::Polynomial => { - formatter.write_fmt(format_args!("Polynomial Error")) - } - Self::Multithreading => { - formatter.write_fmt(format_args!("Multithreading Error")) - } - Self::Processing => { - formatter.write_fmt(format_args!("Processing Error")) - } - Self::Unsupported => { - formatter.write_fmt(format_args!("Something is not supported")) - } - Self::ExternalCommand => { - formatter.write_fmt(format_args!("External Command")) - } - } - } - } - }; -} From 7ecc6ca0b9579a6da802677655aefa3f4cbb557f Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sat, 26 Oct 2024 18:10:13 +0200 Subject: [PATCH 039/129] More RusticErrors Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- Cargo.lock | 12 ++++++ crates/core/Cargo.toml | 4 +- crates/core/src/crypto/aespoly1305.rs | 2 +- crates/core/src/error.rs | 5 ++- crates/core/src/repofile/configfile.rs | 27 ++++++------ crates/core/src/repofile/keyfile.rs | 52 +++++++++++++++++------- crates/core/src/repofile/packfile.rs | 51 ++++++++++++----------- crates/core/src/repofile/snapshotfile.rs | 51 +++++++++++++++-------- 8 files changed, 132 insertions(+), 72 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3c30fa9b..2bcbf619 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2508,6 +2508,17 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + [[package]] name = "path-dedot" version = "3.1.1" @@ -3556,6 +3567,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" dependencies = [ + "password-hash", "pbkdf2", "salsa20", "sha2", diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index b0d03c0f..2605182b 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -58,9 +58,9 @@ pariter = "0.5.1" rayon = "1.10.0" # crypto -aes256ctr_poly1305aes = { version = "0.2.0", features = ["std"] } +aes256ctr_poly1305aes = { version = "0.2.0", features = ["std"] } # we need std here for error impls rand = "0.8.5" -scrypt = { version = "0.11.0", default-features = false } +scrypt = { version = "0.11.0", default-features = false, features = ["std"] } # we need std here for error impls # serialization / packing binrw = "0.14.0" diff --git a/crates/core/src/crypto/aespoly1305.rs b/crates/core/src/crypto/aespoly1305.rs index 75d501ef..35a2e78f 100644 --- a/crates/core/src/crypto/aespoly1305.rs +++ b/crates/core/src/crypto/aespoly1305.rs @@ -100,7 +100,7 @@ impl CryptoKey for Key { err, ) .attach_context("nonce", format!("{nonce:?}")) - .attach_error_code("C001".into()) + .attach_error_code("C001") }) } diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index b84b6cab..a30b9f5d 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -374,8 +374,6 @@ pub enum ErrorKind { Index, /// Key Error Key, - /// Blob Error - Blob, /// Crypto Error Cryptography, /// Compression Error @@ -398,6 +396,9 @@ pub enum ErrorKind { ExternalCommand, /// Virtual File System Error Vfs, + /// Blob, Pack, or Tree Error + // These are deep errors that are not expected to be handled by the user. + Internal, // /// The repository password is incorrect. Please try again. // IncorrectRepositoryPassword, // /// No repository given. Please use the --repository option. diff --git a/crates/core/src/repofile/configfile.rs b/crates/core/src/repofile/configfile.rs index 8a19899f..df8c9cec 100644 --- a/crates/core/src/repofile/configfile.rs +++ b/crates/core/src/repofile/configfile.rs @@ -5,7 +5,7 @@ use serde_with::skip_serializing_none; use crate::{ backend::FileType, blob::BlobType, define_new_id_struct, error::RusticResult, impl_repofile, - repofile::RepoFile, + repofile::RepoFile, ErrorKind, RusticError, }; /// [`ConfigFileErrorKind`] describes the errors that can be returned for `ConfigFile`s @@ -157,12 +157,15 @@ impl ConfigFile { /// /// [`ConfigFileErrorKind::ParsingFailedForPolynomial`]: ConfigFileErrorKind::ParsingFailedForPolynomial pub fn poly(&self) -> RusticResult { - Ok(u64::from_str_radix(&self.chunker_polynomial, 16) - .map_err(|err| ConfigFileErrorKind::ParsingFailedForPolynomial { - polynomial: self.chunker_polynomial.clone(), - source: err, - }) - .map_err(|_err| todo!("Error transition"))?) + let chunker_poly = u64::from_str_radix(&self.chunker_polynomial, 16) + .map_err(|err| RusticError::with_source( + ErrorKind::Parsing, + "Parsing u64 from hex failed for polynomial, the value must be a valid hexadecimal string.", + err) + .attach_context("polynomial",&self.chunker_polynomial.to_string())) + ?; + + Ok(chunker_poly) } /// Get the compression level @@ -177,11 +180,11 @@ impl ConfigFile { (1, _) | (2, Some(0)) => Ok(None), (2, None) => Ok(Some(0)), // use default (=0) zstd compression (2, Some(c)) => Ok(Some(c)), - _ => Err(ConfigFileErrorKind::ConfigVersionNotSupported { - version: self.version, - compression: self.compression, - }) - .map_err(|_err| todo!("Error transition")), + _ => Err(RusticError::new( + ErrorKind::Config, + "Config version not supported. Please make sure, that you use the correct version.", + ) + .attach_context("version", self.version.to_string())), } } diff --git a/crates/core/src/repofile/keyfile.rs b/crates/core/src/repofile/keyfile.rs index f0964b06..b9c9416c 100644 --- a/crates/core/src/repofile/keyfile.rs +++ b/crates/core/src/repofile/keyfile.rs @@ -114,18 +114,33 @@ impl KeyFile { /// [`KeyFileErrorKind::OutputLengthInvalid`]: crate::error::KeyFileErrorKind::OutputLengthInvalid pub fn kdf_key(&self, passwd: &impl AsRef<[u8]>) -> RusticResult { let params = Params::new( - log_2(self.n).map_err(|_err| todo!("Error transition"))?, + log_2(self.n).map_err(|err| { + RusticError::with_source( + ErrorKind::Conversion, + "Calculating log2 failed. Please check the key file and password.", + err, + ) + })?, self.r, self.p, Params::RECOMMENDED_LEN, ) - .map_err(KeyFileErrorKind::InvalidSCryptParameters) - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| { + RusticError::with_source( + ErrorKind::Key, + "Invalid scrypt parameters. Please check the key file and password.", + err, + ) + })?; let mut key = [0; 64]; - scrypt::scrypt(passwd.as_ref(), &self.salt, ¶ms, &mut key) - .map_err(KeyFileErrorKind::OutputLengthInvalid) - .map_err(|_err| todo!("Error transition"))?; + scrypt::scrypt(passwd.as_ref(), &self.salt, ¶ms, &mut key).map_err(|err| { + RusticError::with_source( + ErrorKind::Key, + "Output length invalid. Please check the key file and password.", + err, + ) + })?; Ok(Key::from_slice(&key)) } @@ -216,18 +231,25 @@ impl KeyFile { thread_rng().fill_bytes(&mut salt); let mut key = [0; 64]; - scrypt::scrypt(passwd.as_ref(), &salt, ¶ms, &mut key) - .map_err(KeyFileErrorKind::OutputLengthInvalid) - .map_err(|_err| todo!("Error transition"))?; + scrypt::scrypt(passwd.as_ref(), &salt, ¶ms, &mut key).map_err(|err| { + RusticError::with_source( + ErrorKind::Key, + "Output length invalid. Please check the key file and password.", + err, + ) + })?; let key = Key::from_slice(&key); - let data = key - .encrypt_data( - &serde_json::to_vec(&masterkey) - .map_err(KeyFileErrorKind::CouldNotSerializeAsJsonByteVector) - .map_err(|_err| todo!("Error transition"))?, + + let json_byte_vec = serde_json::to_vec(&masterkey).map_err(|err| { + RusticError::with_source( + ErrorKind::Key, + "Could not serialize as JSON byte vector. This is a bug, please report it.", + err, ) - .map_err(|_err| todo!("Error transition"))?; + })?; + + let data = key.encrypt_data(&json_byte_vec)?; Ok(Self { hostname, diff --git a/crates/core/src/repofile/packfile.rs b/crates/core/src/repofile/packfile.rs index 741b7a24..26ec384b 100644 --- a/crates/core/src/repofile/packfile.rs +++ b/crates/core/src/repofile/packfile.rs @@ -10,6 +10,7 @@ use crate::{ id::Id, impl_repoid, repofile::indexfile::{IndexBlob, IndexPack}, + ErrorKind, RusticError, }; /// [`PackFileErrorKind`] describes the errors that can be returned for `PackFile`s @@ -22,10 +23,6 @@ pub enum PackFileErrorKind { WritingBinaryRepresentationFailed(binrw::Error), /// Read header length is too large! Length: `{size_real}`, file size: `{pack_size}` HeaderLengthTooLarge { size_real: u32, pack_size: u32 }, - /// Read header length doesn't match header contents! Length: `{size_real}`, computed: `{size_computed}` - HeaderLengthDoesNotMatchHeaderContents { size_real: u32, size_computed: u32 }, - /// pack size computed from header doesn't match real pack isch! Computed: `{size_computed}`, real: `{size_real}` - HeaderPackSizeComputedDoesNotMatchRealPackFile { size_real: u32, size_computed: u32 }, /// decrypting from binary failed BinaryDecryptionFailed, /// Partial read of PackFile failed @@ -302,16 +299,23 @@ impl PackHeader { // get header length from the file let size_real = PackHeaderLength::from_binary(&data.split_off(size_guess as usize)) - .map_err(|_err| todo!("Error transition"))? + .map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Reading pack header length failed", + err, + ) + })? .to_u32(); trace!("header size: {size_real}"); if size_real + constants::LENGTH_LEN > pack_size { - return Err(PackFileErrorKind::HeaderLengthTooLarge { - size_real, - pack_size, - }) - .map_err(|_err| todo!("Error transition")); + return Err( + RusticError::new(ErrorKind::Internal, "Read header length is too large!") + .attach_context("size real", size_real.to_string()) + .attach_context("pack size", pack_size.to_string()) + .attach_context("length field value", constants::LENGTH_LEN.to_string()), + ); } // now read the header @@ -324,25 +328,26 @@ impl PackHeader { be.read_partial(FileType::Pack, &id, false, offset, size_real)? }; - let header = - Self::from_binary(&be.decrypt(&data)?).map_err(|_err| todo!("Error transition"))?; + let header = Self::from_binary(&be.decrypt(&data)?).map_err(|err| { + RusticError::with_source(ErrorKind::Internal, "Reading pack header failed.", err) + })?; if header.size() != size_real { - return Err(PackFileErrorKind::HeaderLengthDoesNotMatchHeaderContents { - size_real, - size_computed: header.size(), - }) - .map_err(|_err| todo!("Error transition")); + return Err(RusticError::new( + ErrorKind::Internal, + "Read header length doesn't match header contents!", + ) + .attach_context("size real", size_real.to_string()) + .attach_context("size computed", header.size().to_string())); } if header.pack_size() != pack_size { - return Err( - PackFileErrorKind::HeaderPackSizeComputedDoesNotMatchRealPackFile { - size_real: pack_size, - size_computed: header.pack_size(), - }, + return Err(RusticError::new( + ErrorKind::Internal, + "pack size computed from header doesn't match real pack file size!", ) - .map_err(|_err| todo!("Error transition")); + .attach_context("size real", pack_size.to_string()) + .attach_context("size computed", header.pack_size().to_string())); } Ok(header) diff --git a/crates/core/src/repofile/snapshotfile.rs b/crates/core/src/repofile/snapshotfile.rs index db047454..788ab5f0 100644 --- a/crates/core/src/repofile/snapshotfile.rs +++ b/crates/core/src/repofile/snapshotfile.rs @@ -25,7 +25,7 @@ use crate::{ impl_repofile, progress::Progress, repofile::RepoFile, - Id, + ErrorKind, Id, RusticError, }; #[cfg(feature = "clap")] @@ -45,8 +45,6 @@ pub enum SnapshotFileErrorKind { ValueNotAllowed(String), /// datetime out of range: `{0:?}` OutOfRange(OutOfRangeError), - /// reading the description file failed: `{0:?}` - ReadingDescriptionFailed(std::io::Error), /// getting the SnapshotFile from the backend failed GettingSnapshotFileFailed, /// getting the SnapshotFile by ID failed @@ -151,8 +149,14 @@ impl SnapshotOptions { /// /// [`SnapshotFileErrorKind::NonUnicodeTag`]: crate::error::SnapshotFileErrorKind::NonUnicodeTag pub fn add_tags(mut self, tag: &str) -> RusticResult { - self.tags - .push(StringList::from_str(tag).map_err(|_err| todo!("Error transition"))?); + self.tags.push(StringList::from_str(tag).map_err(|err| { + RusticError::with_source( + ErrorKind::Parsing, + "Failed to create string list from tag. The value must be a valid unicode string.", + err, + ) + .attach_context("tag", tag) + })?); Ok(self) } @@ -407,8 +411,13 @@ impl SnapshotFile { let hostname = gethostname(); hostname .to_str() - .ok_or_else(|| SnapshotFileErrorKind::NonUnicodeHostname(hostname.clone())) - .map_err(|_err| todo!("Error transition"))? + .ok_or_else(|| { + RusticError::new( + ErrorKind::Conversion, + "Failed to convert hostname to string. The value must be a valid unicode string.", + ) + .attach_context("hostname", hostname.to_string_lossy().to_string()) + })? .to_string() }; @@ -416,10 +425,15 @@ impl SnapshotFile { let delete = match (opts.delete_never, opts.delete_after) { (true, _) => DeleteOption::Never, - (_, Some(d)) => DeleteOption::After( - time + Duration::from_std(*d) - .map_err(SnapshotFileErrorKind::OutOfRange) - .map_err(|_err| todo!("Error transition"))?, + (_, Some(duration)) => DeleteOption::After( + time + Duration::from_std(*duration).map_err(|err| { + RusticError::with_source( + ErrorKind::Conversion, + "Failed to convert duration to std::time::Duration. Please make sure the value is a valid duration string.", + err, + ) + .attach_context("duration", duration.to_string()) + })?, ), (false, None) => DeleteOption::NotSet, }; @@ -448,12 +462,15 @@ impl SnapshotFile { }; // use description from description file if it is given - if let Some(ref file) = opts.description_from { - snap.description = Some( - std::fs::read_to_string(file) - .map_err(SnapshotFileErrorKind::ReadingDescriptionFailed) - .map_err(|_err| todo!("Error transition"))?, - ); + if let Some(ref path) = opts.description_from { + snap.description = Some(std::fs::read_to_string(path).map_err(|err| { + RusticError::with_source( + ErrorKind::Io, + "Failed to read description file. Please make sure the file exists and is readable.", + err, + ) + .attach_context("path", path.to_string_lossy().to_string()) + })?); } _ = snap.set_tags(opts.tags.clone()); From 942df62e88ad330caa05af3d64a0658f44223a49 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sat, 26 Oct 2024 18:32:59 +0200 Subject: [PATCH 040/129] More RusticErrors for Archiver Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/archiver.rs | 7 ---- crates/core/src/archiver/file_archiver.rs | 26 ++++++++++----- crates/core/src/archiver/tree_archiver.rs | 27 ++++++++------- crates/core/src/error.rs | 40 +++++++++++------------ crates/core/src/index.rs | 2 +- crates/core/src/repository.rs | 2 +- 6 files changed, 55 insertions(+), 49 deletions(-) diff --git a/crates/core/src/archiver.rs b/crates/core/src/archiver.rs index 762d6f68..19a6cb1f 100644 --- a/crates/core/src/archiver.rs +++ b/crates/core/src/archiver.rs @@ -31,13 +31,6 @@ use crate::{ pub enum ArchiverErrorKind { /// tree stack empty TreeStackEmpty, - /// cannot open file or directory `{path}` - OpeningFileFailed { - /// path of the file - path: PathBuf, - }, - /// option should contain a value, but contained `None` - UnpackingTreeTypeOptionalFailed, /// couldn't determine size for item in Archiver CouldNotDetermineSize, } diff --git a/crates/core/src/archiver/file_archiver.rs b/crates/core/src/archiver/file_archiver.rs index bb93a481..33166dc3 100644 --- a/crates/core/src/archiver/file_archiver.rs +++ b/crates/core/src/archiver/file_archiver.rs @@ -5,7 +5,6 @@ use crate::{ parent::{ItemWithParent, ParentResult}, tree::TreeType, tree_archiver::TreeItem, - ArchiverErrorKind, }, backend::{ decrypt::DecryptWriteBackend, @@ -19,11 +18,10 @@ use crate::{ cdc::rolling_hash::Rabin64, chunker::ChunkIter, crypto::hasher::hash, - error::RusticResult, + error::{ErrorKind, RusticError, RusticResult}, index::{indexer::SharedIndexer, ReadGlobalIndex}, progress::Progress, repofile::configfile::ConfigFile, - ErrorKind, RusticError, }; /// The `FileArchiver` is responsible for archiving files. @@ -122,11 +120,23 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> FileArchiver<'a, BE, I> { (node, size) } else if node.node_type == NodeType::File { let r = open - .ok_or(ArchiverErrorKind::UnpackingTreeTypeOptionalFailed) - .map_err(|_err| todo!("Error transition"))? + .ok_or( + RusticError::new( + ErrorKind::Internal, + "Failed to unpack tree type optional. Option should contain a value, but contained `None`. This is a bug. Please report it.", + ) + .attach_context("path", path.display().to_string()), + )? .open() - .map_err(|_| ArchiverErrorKind::OpeningFileFailed { path: path.clone() }) - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| { + RusticError::with_source( + ErrorKind::Io, + "Failed to open ReadSourceOpen", + err, + ) + .attach_context("path", path.display().to_string()) + })?; + self.backup_reader(r, node, p)? } else { (node, 0) @@ -156,7 +166,7 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> FileArchiver<'a, BE, I> { self.rabin.clone(), ) .map(|chunk| { - let chunk = chunk.map_err(|_err| todo!("Error transition"))?; + let chunk = chunk?; let id = hash(&chunk); let size = chunk.len() as u64; diff --git a/crates/core/src/archiver/tree_archiver.rs b/crates/core/src/archiver/tree_archiver.rs index 58e703f0..be806417 100644 --- a/crates/core/src/archiver/tree_archiver.rs +++ b/crates/core/src/archiver/tree_archiver.rs @@ -4,16 +4,16 @@ use bytesize::ByteSize; use log::{debug, trace}; use crate::{ - archiver::{parent::ParentResult, tree::TreeType, ArchiverErrorKind}, + archiver::{parent::ParentResult, tree::TreeType}, backend::{decrypt::DecryptWriteBackend, node::Node}, blob::{ packer::Packer, tree::{Tree, TreeId}, BlobType, }, + error::{ErrorKind, RusticError, RusticResult}, index::{indexer::SharedIndexer, ReadGlobalIndex}, repofile::{configfile::ConfigFile, snapshotfile::SnapshotSummary}, - RusticResult, }; pub(crate) type TreeItem = TreeType<(ParentResult<()>, u64), ParentResult>; @@ -107,11 +107,12 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> TreeArchiver<'a, BE, I> { self.stack.push((path, node, parent, tree)); } TreeType::EndTree => { - let (path, mut node, parent, tree) = self - .stack - .pop() - .ok_or_else(|| ArchiverErrorKind::TreeStackEmpty) - .map_err(|_err| todo!("Error transition"))?; + let (path, mut node, parent, tree) = self.stack.pop().ok_or_else(|| { + RusticError::new( + ErrorKind::Internal, + "Tree stack is empty. This is a bug. Please report it.", + ) + })?; // save tree trace!("finishing {path:?}"); @@ -174,10 +175,14 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> TreeArchiver<'a, BE, I> { /// /// [`PackerErrorKind::SendingCrossbeamMessageFailed`]: crate::error::PackerErrorKind::SendingCrossbeamMessageFailed fn backup_tree(&mut self, path: &Path, parent: &ParentResult) -> RusticResult { - let (chunk, id) = self - .tree - .serialize() - .map_err(|_err| todo!("Error transition"))?; + let (chunk, id) = self.tree.serialize().map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to serialize tree. This is a bug. Please report it.", + err, + ) + .attach_context("path", path.to_string_lossy()) + })?; let dirsize = chunk.len() as u64; let dirsize_bytes = ByteSize(dirsize).to_string_as(true); diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index a30b9f5d..c0793267 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -360,45 +360,43 @@ pub enum Status { pub enum ErrorKind { /// Backend Error Backend, - /// IO Error - Io, - /// Password Error - Password, - /// Repository Error - Repository, /// Command Error Command, + /// Compression Error + Compression, /// Config Error Config, - /// Index Error - Index, - /// Key Error - Key, + /// Conversion Error + Conversion, /// Crypto Error Cryptography, - /// Compression Error - Compression, + /// External Command Error + ExternalCommand, + /// Blob, Pack, Index or Tree Error + // These are deep errors that are not expected to be handled by the user. + Internal, + /// IO Error + Io, + /// Key Error + Key, + /// Multithreading Error + Multithreading, /// Parsing Error Parsing, - /// Conversion Error - Conversion, + /// Password Error + Password, /// Permission Error Permission, /// Polynomial Error Polynomial, - /// Multithreading Error - Multithreading, /// Processing Error Processing, + /// Repository Error + Repository, /// Something is not supported Unsupported, - /// External Command Error - ExternalCommand, /// Virtual File System Error Vfs, - /// Blob, Pack, or Tree Error - // These are deep errors that are not expected to be handled by the user. - Internal, // /// The repository password is incorrect. Please try again. // IncorrectRepositoryPassword, // /// No repository given. Please use the --repository option. diff --git a/crates/core/src/index.rs b/crates/core/src/index.rs index 5fd9eff8..f74ce7ac 100644 --- a/crates/core/src/index.rs +++ b/crates/core/src/index.rs @@ -205,7 +205,7 @@ pub trait ReadIndex { self.get_id(tpe, id).map_or_else( || { Err( - RusticError::new(ErrorKind::Index, "Blob not found in index") + RusticError::new(ErrorKind::Internal, "Blob not found in index") .attach_context("blob id", id.to_string()) .attach_context("blob type", tpe.to_string()), ) diff --git a/crates/core/src/repository.rs b/crates/core/src/repository.rs index 2b2a1038..736c7460 100644 --- a/crates/core/src/repository.rs +++ b/crates/core/src/repository.rs @@ -1633,7 +1633,7 @@ impl Repository { let blob_id: BlobId = (*id).into(); let ie = self.index().get_id(T::TYPE, &blob_id).ok_or_else(|| { RusticError::new( - ErrorKind::Index, + ErrorKind::Internal, "BlobID not found in index, but should be there. This is a bug. Please report it.", ) .attach_context("blob id", blob_id.to_string()) From 01f98637bccaa5e9ce4b555fbbbff6798295b64f Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sat, 26 Oct 2024 18:40:35 +0200 Subject: [PATCH 041/129] RusticErrors for DryRun backend Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/backend/dry_run.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/crates/core/src/backend/dry_run.rs b/crates/core/src/backend/dry_run.rs index 6dca6fd1..6fe337a0 100644 --- a/crates/core/src/backend/dry_run.rs +++ b/crates/core/src/backend/dry_run.rs @@ -4,9 +4,9 @@ use zstd::decode_all; use crate::{ backend::{ decrypt::{DecryptFullBackend, DecryptReadBackend, DecryptWriteBackend}, - CryptBackendErrorKind, FileType, ReadBackend, WriteBackend, + FileType, ReadBackend, WriteBackend, }, - error::RusticResult, + error::{ErrorKind, RusticError, RusticResult}, id::Id, }; @@ -63,12 +63,22 @@ impl DecryptReadBackend for DryRunBackend { Ok(match decrypted.first() { Some(b'{' | b'[') => decrypted, // not compressed Some(2) => decode_all(&decrypted[1..]) - .map_err(CryptBackendErrorKind::DecodingZstdCompressedDataFailed) - .map_err(|_err| todo!("Error transition"))?, // 2 indicates compressed data following + .map_err(|err| + RusticError::with_source( + ErrorKind::Compression, + "Decoding zstd compressed data failed. This can happen if the data is corrupted. Please check the backend for corruption and try again. You can also try to run `rustic check` to check for corruption.", + err + ) + ) + ?, // 2 indicates compressed data following _ => { - return Err(CryptBackendErrorKind::DecryptionNotSupportedForBackend) - .map_err(|_err| todo!("Error transition")) + return Err( + RusticError::new( + ErrorKind::Backend, + "Decryption not supported. The data is not in a supported format.", + )); } + } .into()) } From 32b63f4bf146d435a438357a0ff0a72b31d8abc0 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sat, 26 Oct 2024 19:33:52 +0200 Subject: [PATCH 042/129] RusticErrors for LocalDestination Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/backend/local_destination.rs | 29 ++++++++++++++------ 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/crates/core/src/backend/local_destination.rs b/crates/core/src/backend/local_destination.rs index fa1ea636..9b92e8e7 100644 --- a/crates/core/src/backend/local_destination.rs +++ b/crates/core/src/backend/local_destination.rs @@ -28,8 +28,10 @@ use nix::{ use crate::backend::ignore::mapper::map_mode_from_go; #[cfg(not(windows))] use crate::backend::node::NodeType; -use crate::backend::node::{ExtendedAttribute, Metadata, Node}; -use crate::error::RusticResult; +use crate::{ + backend::node::{ExtendedAttribute, Metadata, Node}, + error::{ErrorKind, RusticError, RusticResult}, +}; /// [`LocalDestinationErrorKind`] describes the errors that can be returned by an action on the filesystem in Backends #[derive(thiserror::Error, Debug, displaydoc::Display)] @@ -150,17 +152,28 @@ impl LocalDestination { let path: PathBuf = path.into(); let is_file = path.is_file() || (!path.is_dir() && !is_dir && expect_file); + // FIXME: Refactor logic to avoid duplication if create { if is_file { if let Some(path) = path.parent() { - fs::create_dir_all(path) - .map_err(LocalDestinationErrorKind::DirectoryCreationFailed) - .map_err(|_err| todo!("Error transition"))?; + fs::create_dir_all(path).map_err(|err| { + RusticError::with_source( + ErrorKind::Io, + "The directory could not be created.", + err, + ) + .attach_context("path", path.display().to_string()) + })?; } } else { - fs::create_dir_all(&path) - .map_err(LocalDestinationErrorKind::DirectoryCreationFailed) - .map_err(|_err| todo!("Error transition"))?; + fs::create_dir_all(&path).map_err(|err| { + RusticError::with_source( + ErrorKind::Io, + "The directory could not be created.", + err, + ) + .attach_context("path", path.display().to_string()) + })?; } } From 4e9c4083a13d1f11e3c3a37dd7e00456c1974fff Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sat, 26 Oct 2024 19:34:10 +0200 Subject: [PATCH 043/129] RusticErrors for Packer and Tree Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/blob/packer.rs | 27 +++++- crates/core/src/blob/tree.rs | 158 ++++++++++++++++++++++++++------- 2 files changed, 150 insertions(+), 35 deletions(-) diff --git a/crates/core/src/blob/packer.rs b/crates/core/src/blob/packer.rs index bf31b5ed..090c07e1 100644 --- a/crates/core/src/blob/packer.rs +++ b/crates/core/src/blob/packer.rs @@ -26,6 +26,7 @@ use crate::{ packfile::{PackHeaderLength, PackHeaderRef, PackId}, snapshotfile::SnapshotSummary, }, + ErrorKind, RusticError, }; /// [`PackerErrorKind`] describes the errors that can be returned for a Packer @@ -497,8 +498,16 @@ impl RawPacker { /// /// If the packfile could not be saved fn finalize(&mut self) -> RusticResult { - self.save().map_err(|_err| todo!("Error transition"))?; + self.save().map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to save packfile. Data may be lost. Please report this issue and upload the log file.", + err, + ) + })?; + self.file_writer.take().unwrap().finalize()?; + Ok(std::mem::take(&mut self.stats)) } @@ -857,7 +866,13 @@ impl Repacker { blob.uncompressed_length, Some(self.size_limit), ) - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to fast-add (unchecked) blob to packfile.", + err, + ) + })?; Ok(()) } @@ -884,7 +899,13 @@ impl Repacker { self.packer .add_with_sizelimit(data, blob.id, Some(self.size_limit)) - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to add blob to packfile.", + err, + ) + })?; Ok(()) } diff --git a/crates/core/src/blob/tree.rs b/crates/core/src/blob/tree.rs index 7d1338b6..c3fbe4ff 100644 --- a/crates/core/src/blob/tree.rs +++ b/crates/core/src/blob/tree.rs @@ -27,6 +27,7 @@ use crate::{ index::ReadGlobalIndex, progress::Progress, repofile::snapshotfile::SnapshotSummary, + ErrorKind, RusticError, }; /// [`TreeErrorKind`] describes the errors that can come up dealing with Trees @@ -148,13 +149,19 @@ impl Tree { ) -> RusticResult { let data = index .get_tree(&id) - .ok_or_else(|| TreeErrorKind::BlobIdNotFound(id)) - .map_err(|_err| todo!("Error transition"))? + .ok_or_else(|| { + RusticError::new(ErrorKind::Internal, "Blob ID not found in index") + .attach_context("tree id", id.to_string()) + })? .read_data(be)?; - let tree = serde_json::from_slice(&data) - .map_err(TreeErrorKind::DeserializingTreeFailed) - .map_err(|_err| todo!("Error transition"))?; + let tree = serde_json::from_slice(&data).map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to deserialize tree from JSON. This is a bug. Please report it.", + err, + ) + })?; Ok(tree) } @@ -186,18 +193,35 @@ impl Tree { node.subtree = Some(id); for p in path.components() { - if let Some(p) = comp_to_osstr(p).map_err(|_err| todo!("Error transition"))? { + if let Some(p) = comp_to_osstr(p).map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to convert Path component to OsString. This is a bug. Please report it.", + err, + ) + .attach_context("path", path.display().to_string()) + })? { let id = node .subtree - .ok_or_else(|| TreeErrorKind::NotADirectory(p.clone())) - .map_err(|_err| todo!("Error transition"))?; + .ok_or_else(|| + RusticError::new( + ErrorKind::Internal, + "Node is not a directory. This is a bug. Please report it.", + ).attach_context("node", p.to_string_lossy().to_string()) + ) + ?; let tree = Self::from_backend(be, index, id)?; node = tree .nodes .into_iter() .find(|node| node.name() == p) - .ok_or_else(|| TreeErrorKind::PathNotFound(p.clone())) - .map_err(|_err| todo!("Error transition"))?; + .ok_or_else(|| + RusticError::new( + ErrorKind::Internal, + "Node not found in tree. This is a bug. Please report it.", + ).attach_context("node", p.to_string_lossy().to_string()) + ) + ?; } } @@ -237,8 +261,12 @@ impl Tree { } else { let id = node .subtree - .ok_or_else(|| TreeErrorKind::NotADirectory(path_comp[idx].clone())) - .map_err(|_err| todo!("Error transition"))?; + .ok_or_else(|| + RusticError::new( + ErrorKind::Internal, + "Subtree ID not found. This is a bug. Please report it.", + ).attach_context("node", path_comp[idx].to_string_lossy().to_string()) + )?; find_node_from_component( be, @@ -261,7 +289,13 @@ impl Tree { .components() .filter_map(|p| comp_to_osstr(p).transpose()) .collect::>() - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| + RusticError::with_source( + ErrorKind::Internal, + "Failed to convert Path component to OsString. This is a bug. Please report it.", + err, + ).attach_context("path", path.display().to_string()) + )?; // caching all results let mut results_cache = vec![BTreeMap::new(); path_comp.len()]; @@ -335,8 +369,13 @@ impl Tree { if node.is_dir() { let id = node .subtree - .ok_or_else(|| TreeErrorKind::NotADirectory(node.name())) - .map_err(|_err| todo!("Error transition"))?; + .ok_or_else(|| + RusticError::new( + ErrorKind::Internal, + "Subtree ID not found. This is a bug. Please report it.", + ).attach_context("node", node.name().to_string_lossy().to_string()) + )?; + result.append(&mut find_matching_nodes_recursive( be, index, id, &node_path, state, matches, )?); @@ -588,50 +627,97 @@ where for g in &opts.glob { _ = override_builder .add(g) - .map_err(TreeErrorKind::BuildingNodeStreamerFailed) - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| + RusticError::with_source( + ErrorKind::Internal, + "Failed to add glob pattern to override builder. This is a bug. Please report it.", + err, + ) + .attach_context("glob", g.to_string()) + )?; } for file in &opts.glob_file { for line in std::fs::read_to_string(file) - .map_err(TreeErrorKind::ReadingFileStringFromGlobsFailed) - .map_err(|_err| todo!("Error transition"))? + .map_err(|err| + RusticError::with_source( + ErrorKind::Internal, + "Failed to read string from glob file. This is a bug. Please report it.", + err, + ) + .attach_context("glob file", file.to_string()) + )? .lines() { _ = override_builder .add(line) - .map_err(TreeErrorKind::BuildingNodeStreamerFailed) - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| + RusticError::with_source( + ErrorKind::Internal, + "Failed to add glob pattern line to override builder. This is a bug. Please report it.", + err, + ) + .attach_context("glob pattern line", line.to_string()) + )?; + } } _ = override_builder .case_insensitive(true) - .map_err(TreeErrorKind::BuildingNodeStreamerFailed) - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| + RusticError::with_source( + ErrorKind::Internal, + "Failed to set case insensitivity in override builder. This is a bug. Please report it.", + err, + ) + )?; for g in &opts.iglob { _ = override_builder .add(g) - .map_err(TreeErrorKind::BuildingNodeStreamerFailed) - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| + RusticError::with_source( + ErrorKind::Internal, + "Failed to add iglob pattern to override builder. This is a bug. Please report it.", + err, + ) + .attach_context("iglob", g.to_string()) + )?; } for file in &opts.iglob_file { for line in std::fs::read_to_string(file) - .map_err(TreeErrorKind::ReadingFileStringFromGlobsFailed) - .map_err(|_err| todo!("Error transition"))? + .map_err(|err| + RusticError::with_source( + ErrorKind::Internal, + "Failed to read string from iglob file. This is a bug. Please report it.", + err, + ) + .attach_context("iglob file", file.to_string()) + )? .lines() { _ = override_builder .add(line) - .map_err(TreeErrorKind::BuildingNodeStreamerFailed) - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| + RusticError::with_source( + ErrorKind::Internal, + "Failed to add iglob pattern line to override builder. This is a bug. Please report it.", + err, + ) + .attach_context("iglob pattern line", line.to_string()) + )?; } } let overrides = override_builder .build() - .map_err(TreeErrorKind::BuildingNodeStreamerFailed) - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| + RusticError::with_source( + ErrorKind::Internal, + "Failed to build matcher for a set of glob overrides. This is a bug. Please report it.", + err, + ) + )?; Self::new_streamer(be, index, node, Some(overrides), opts.recursive) } @@ -761,7 +847,15 @@ impl TreeStreamerOnce

{ for (count, id) in ids.into_iter().enumerate() { if !streamer .add_pending(PathBuf::new(), id, count) - .map_err(|_err| todo!("Error transition"))? + .map_err(|err| + RusticError::with_source( + ErrorKind::Internal, + "Failed to add tree ID to pending queue. This is a bug. Please report it.", + err, + ) + .attach_context("tree id", id.to_string()) + .attach_context("count", count.to_string()) + )? { streamer.p.inc(1); streamer.finished_ids += 1; From db82ed21db158e66ef4f8f3e6ba8c8b52d2fd42a Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sat, 26 Oct 2024 19:58:44 +0200 Subject: [PATCH 044/129] RusticErrors for Cache backend Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/backend/cache.rs | 219 ++++++++++++++++++------------- 1 file changed, 125 insertions(+), 94 deletions(-) diff --git a/crates/core/src/backend/cache.rs b/crates/core/src/backend/cache.rs index 60dd8e48..57aa3bc9 100644 --- a/crates/core/src/backend/cache.rs +++ b/crates/core/src/backend/cache.rs @@ -1,7 +1,7 @@ use std::{ collections::HashMap, fs::{self, File}, - io::{ErrorKind, Read, Seek, SeekFrom, Write}, + io::{self, Read, Seek, SeekFrom, Write}, path::PathBuf, sync::Arc, }; @@ -13,32 +13,11 @@ use walkdir::WalkDir; use crate::{ backend::{FileType, ReadBackend, WriteBackend}, - error::RusticResult, + error::{ErrorKind, RusticError, RusticResult}, id::Id, repofile::configfile::RepositoryId, }; -/// [`CacheBackendErrorKind`] describes the errors that can be returned by a Caching action in Backends -#[derive(thiserror::Error, Debug, displaydoc::Display)] -pub enum CacheBackendErrorKind { - /// Cache directory could not be determined, please set the environment variable XDG_CACHE_HOME or HOME! - NoCacheDirectory, - /// Error in cache backend {context} for {tpe:?} with {id}: {source} - Io { - context: String, - source: std::io::Error, - tpe: Option, - id: Id, - }, - /// Ensuring tag failed for cache directory {path}: {source} - EnsureTagFailed { - source: std::io::Error, - path: PathBuf, - }, -} - -pub(crate) type CacheBackendResult = Result; - /// Backend that caches data. /// /// This backend caches data in a directory. @@ -260,39 +239,50 @@ impl Cache { let mut path = if let Some(p) = path { p } else { - let mut dir = cache_dir() - .ok_or_else(|| CacheBackendErrorKind::NoCacheDirectory) - .map_err(|_err| todo!("Error transition"))?; + let mut dir = cache_dir().ok_or_else(|| + RusticError::new( + ErrorKind::Backend, + "Cache directory could not be determined, please set the environment variable XDG_CACHE_HOME or HOME!" + ) + )?; dir.push("rustic"); dir }; fs::create_dir_all(&path) - .map_err(|err| CacheBackendErrorKind::Io { - context: "while creating cache directory".into(), - source: err, - tpe: None, - id: id.clone().into_inner(), - }) - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| + RusticError::with_source( + ErrorKind::Io, + "Failed to create cache directory", + err + ) + .attach_context("path", path.display().to_string()) + .attach_context("id", id.to_string()) + )?; cachedir::ensure_tag(&path) - .map_err(|err| CacheBackendErrorKind::EnsureTagFailed { - source: err, - path: path.clone(), - }) - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| + RusticError::with_source( + ErrorKind::Io, + "Failed to ensure cache directory tag", + err + ) + .attach_context("path", path.display().to_string()) + .attach_context("id", id.to_string()) + )?; path.push(id.to_hex()); fs::create_dir_all(&path) - .map_err(|err| CacheBackendErrorKind::Io { - context: "while creating cache directory with id".into(), - source: err, - tpe: None, - id: id.clone().into_inner(), - }) - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| + RusticError::with_source( + ErrorKind::Io, + "Failed to create cache directory with id", + err + ) + .attach_context("path", path.display().to_string()) + .attach_context("id", id.to_string()) + )?; Ok(Self { path }) } @@ -420,20 +410,26 @@ impl Cache { /// [`CacheBackendErrorKind::FromIoError`]: crate::error::CacheBackendErrorKind::FromIoError pub fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult> { trace!("cache reading tpe: {:?}, id: {}", &tpe, &id); - match fs::read(self.path(tpe, id)) { + + let path = self.path(tpe, id); + + match fs::read(&path) { Ok(data) => { trace!("cache hit!"); Ok(Some(data.into())) } - Err(err) if err.kind() == ErrorKind::NotFound => Ok(None), - Err(err) => Err(CacheBackendErrorKind::Io { - context: "while reading full data of file".into(), - source: err, - tpe: Some(tpe.clone()), - id: id.clone(), - }) - .map_err(|_err| todo!("Error transition")), - } + Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None), + Err(err) => Err( + RusticError::with_source( + ErrorKind::Io, + "Failed to read full data of file", + err + ) + .attach_context("path", path.display().to_string()) + .attach_context("tpe", tpe.to_string()) + .attach_context("id", id.to_string()) + ) + } } /// Reads partial data of the given file. @@ -463,26 +459,49 @@ impl Cache { &id, &offset ); + let mut file = match File::open(self.path(tpe, id)) { Ok(file) => file, - Err(err) if err.kind() == ErrorKind::NotFound => return Ok(None), + Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(None), Err(err) => { - return Err(CacheBackendErrorKind::Io { - context: "while opening file".into(), - source: err, - tpe: Some(tpe.clone()), - id: id.clone(), - }) - .map_err(|_err| todo!("Error transition")) + return Err( + RusticError::with_source( + ErrorKind::Io, + "Failed to open file", + err + ) + .attach_context("tpe", tpe.to_string()) + .attach_context("id", id.to_string()) + ) } }; + _ = file .seek(SeekFrom::Start(u64::from(offset))) - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| { + RusticError::with_source(ErrorKind::Io, "Failed to seek in file", err) + .attach_context("tpe", tpe.to_string()) + .attach_context("id", id.to_string()) + .attach_context("offset", offset.to_string()) + })?; + let mut vec = vec![0; length as usize]; + file.read_exact(&mut vec) - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| + RusticError::with_source( + ErrorKind::Io, + "Failed to read from file", + err + ) + .attach_context("tpe", tpe.to_string()) + .attach_context("id", id.to_string()) + .attach_context("offset", offset.to_string()) + .attach_context("length", length.to_string()) + )?; + trace!("cache hit!"); + Ok(Some(vec.into())) } @@ -502,14 +521,19 @@ impl Cache { pub fn write_bytes(&self, tpe: FileType, id: &Id, buf: &Bytes) -> RusticResult<()> { trace!("cache writing tpe: {:?}, id: {}", &tpe, &id); - fs::create_dir_all(self.dir(tpe, id)) - .map_err(|err| CacheBackendErrorKind::Io { - context: "while creating directories".into(), - source: err, - tpe: Some(tpe.clone()), - id: id.clone(), - }) - .map_err(|_err| todo!("Error transition"))?; + let dir = self.dir(tpe, id); + + fs::create_dir_all(&dir) + .map_err(|err| + RusticError::with_source( + ErrorKind::Io, + "Failed to create directories", + err + ) + .attach_context("path", dir.display().to_string()) + .attach_context("tpe", tpe.to_string()) + .attach_context("id", id.to_string()) + )?; let filename = self.path(tpe, id); @@ -518,22 +542,26 @@ impl Cache { .truncate(true) .write(true) .open(&filename) - .map_err(|err| CacheBackendErrorKind::Io { - context: "while opening file paths".into(), - source: err, - tpe: Some(tpe.clone()), - id: id.clone(), - }) - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| + RusticError::with_source( + ErrorKind::Io, + "Failed to open file", + err + ) + .attach_context("path", filename.display().to_string()) + )?; file.write_all(buf) - .map_err(|err| CacheBackendErrorKind::Io { - context: "while writing to buffer".into(), - source: err, - tpe: Some(tpe.clone()), - id: id.clone(), - }) - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| + RusticError::with_source( + ErrorKind::Io, + "Failed to write to buffer", + err + ) + .attach_context("path", filename.display().to_string()) + .attach_context("tpe", tpe.to_string()) + .attach_context("id", id.to_string()) + )?; Ok(()) } @@ -554,13 +582,16 @@ impl Cache { trace!("cache writing tpe: {:?}, id: {}", &tpe, &id); let filename = self.path(tpe, id); fs::remove_file(&filename) - .map_err(|err| CacheBackendErrorKind::Io { - context: format!("while removing file: {filename:?}"), - source: err, - tpe: Some(tpe.clone()), - id: id.clone(), - }) - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| + RusticError::with_source( + ErrorKind::Io, + "Failed to remove file", + err + ) + .attach_context("path", filename.display().to_string()) + .attach_context("tpe", tpe.to_string()) + .attach_context("id", id.to_string()) + )?; Ok(()) } From 6461492d44d36f308bc3e9d6b04553d1b663a51b Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sat, 26 Oct 2024 20:41:25 +0200 Subject: [PATCH 045/129] More RusticErrors Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/archiver/file_archiver.rs | 2 +- crates/core/src/backend/cache.rs | 139 ++++++++-------------- crates/core/src/backend/decrypt.rs | 119 ++++++++++++------ crates/core/src/backend/dry_run.rs | 3 +- crates/core/src/backend/ignore.rs | 91 +++++++++++--- crates/core/src/blob/tree.rs | 54 ++++----- crates/core/src/chunker.rs | 2 +- crates/core/src/commands/restore.rs | 2 +- crates/core/src/error.rs | 6 +- crates/core/src/repofile/keyfile.rs | 2 +- crates/core/src/repofile/snapshotfile.rs | 4 +- crates/core/src/repository.rs | 2 +- crates/core/tests/errors.rs | 7 +- 13 files changed, 247 insertions(+), 186 deletions(-) diff --git a/crates/core/src/archiver/file_archiver.rs b/crates/core/src/archiver/file_archiver.rs index 33166dc3..71da1ca6 100644 --- a/crates/core/src/archiver/file_archiver.rs +++ b/crates/core/src/archiver/file_archiver.rs @@ -157,7 +157,7 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> FileArchiver<'a, BE, I> { r, usize::try_from(node.meta.size).map_err(|err| { RusticError::with_source( - ErrorKind::Conversion, + ErrorKind::Internal, "Failed to convert node size to usize", err, ) diff --git a/crates/core/src/backend/cache.rs b/crates/core/src/backend/cache.rs index 57aa3bc9..96a75c41 100644 --- a/crates/core/src/backend/cache.rs +++ b/crates/core/src/backend/cache.rs @@ -239,7 +239,7 @@ impl Cache { let mut path = if let Some(p) = path { p } else { - let mut dir = cache_dir().ok_or_else(|| + let mut dir = cache_dir().ok_or_else(|| RusticError::new( ErrorKind::Backend, "Cache directory could not be determined, please set the environment variable XDG_CACHE_HOME or HOME!" @@ -249,40 +249,29 @@ impl Cache { dir }; - fs::create_dir_all(&path) - .map_err(|err| - RusticError::with_source( - ErrorKind::Io, - "Failed to create cache directory", - err - ) + fs::create_dir_all(&path).map_err(|err| { + RusticError::with_source(ErrorKind::Io, "Failed to create cache directory", err) .attach_context("path", path.display().to_string()) .attach_context("id", id.to_string()) - )?; - - cachedir::ensure_tag(&path) - .map_err(|err| - RusticError::with_source( - ErrorKind::Io, - "Failed to ensure cache directory tag", - err - ) + })?; + + cachedir::ensure_tag(&path).map_err(|err| { + RusticError::with_source(ErrorKind::Io, "Failed to ensure cache directory tag", err) .attach_context("path", path.display().to_string()) .attach_context("id", id.to_string()) - )?; + })?; path.push(id.to_hex()); - fs::create_dir_all(&path) - .map_err(|err| - RusticError::with_source( - ErrorKind::Io, - "Failed to create cache directory with id", - err - ) - .attach_context("path", path.display().to_string()) - .attach_context("id", id.to_string()) - )?; + fs::create_dir_all(&path).map_err(|err| { + RusticError::with_source( + ErrorKind::Io, + "Failed to create cache directory with id", + err, + ) + .attach_context("path", path.display().to_string()) + .attach_context("id", id.to_string()) + })?; Ok(Self { path }) } @@ -410,26 +399,24 @@ impl Cache { /// [`CacheBackendErrorKind::FromIoError`]: crate::error::CacheBackendErrorKind::FromIoError pub fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult> { trace!("cache reading tpe: {:?}, id: {}", &tpe, &id); - + let path = self.path(tpe, id); - + match fs::read(&path) { Ok(data) => { trace!("cache hit!"); Ok(Some(data.into())) } Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None), - Err(err) => Err( - RusticError::with_source( - ErrorKind::Io, - "Failed to read full data of file", - err - ) - .attach_context("path", path.display().to_string()) - .attach_context("tpe", tpe.to_string()) - .attach_context("id", id.to_string()) - ) - } + Err(err) => Err(RusticError::with_source( + ErrorKind::Io, + "Failed to read full data of file", + err, + ) + .attach_context("path", path.display().to_string()) + .attach_context("tpe", tpe.to_string()) + .attach_context("id", id.to_string())), + } } /// Reads partial data of the given file. @@ -465,14 +452,10 @@ impl Cache { Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(None), Err(err) => { return Err( - RusticError::with_source( - ErrorKind::Io, - "Failed to open file", - err - ) - .attach_context("tpe", tpe.to_string()) - .attach_context("id", id.to_string()) - ) + RusticError::with_source(ErrorKind::Io, "Failed to open file", err) + .attach_context("tpe", tpe.to_string()) + .attach_context("id", id.to_string()), + ) } }; @@ -487,18 +470,13 @@ impl Cache { let mut vec = vec![0; length as usize]; - file.read_exact(&mut vec) - .map_err(|err| - RusticError::with_source( - ErrorKind::Io, - "Failed to read from file", - err - ) + file.read_exact(&mut vec).map_err(|err| { + RusticError::with_source(ErrorKind::Io, "Failed to read from file", err) .attach_context("tpe", tpe.to_string()) .attach_context("id", id.to_string()) .attach_context("offset", offset.to_string()) .attach_context("length", length.to_string()) - )?; + })?; trace!("cache hit!"); @@ -523,17 +501,12 @@ impl Cache { let dir = self.dir(tpe, id); - fs::create_dir_all(&dir) - .map_err(|err| - RusticError::with_source( - ErrorKind::Io, - "Failed to create directories", - err - ) + fs::create_dir_all(&dir).map_err(|err| { + RusticError::with_source(ErrorKind::Io, "Failed to create directories", err) .attach_context("path", dir.display().to_string()) .attach_context("tpe", tpe.to_string()) .attach_context("id", id.to_string()) - )?; + })?; let filename = self.path(tpe, id); @@ -542,26 +515,17 @@ impl Cache { .truncate(true) .write(true) .open(&filename) - .map_err(|err| - RusticError::with_source( - ErrorKind::Io, - "Failed to open file", - err - ) - .attach_context("path", filename.display().to_string()) - )?; - - file.write_all(buf) - .map_err(|err| - RusticError::with_source( - ErrorKind::Io, - "Failed to write to buffer", - err - ) + .map_err(|err| { + RusticError::with_source(ErrorKind::Io, "Failed to open file", err) + .attach_context("path", filename.display().to_string()) + })?; + + file.write_all(buf).map_err(|err| { + RusticError::with_source(ErrorKind::Io, "Failed to write to buffer", err) .attach_context("path", filename.display().to_string()) .attach_context("tpe", tpe.to_string()) .attach_context("id", id.to_string()) - )?; + })?; Ok(()) } @@ -581,17 +545,12 @@ impl Cache { pub fn remove(&self, tpe: FileType, id: &Id) -> RusticResult<()> { trace!("cache writing tpe: {:?}, id: {}", &tpe, &id); let filename = self.path(tpe, id); - fs::remove_file(&filename) - .map_err(|err| - RusticError::with_source( - ErrorKind::Io, - "Failed to remove file", - err - ) + fs::remove_file(&filename).map_err(|err| { + RusticError::with_source(ErrorKind::Io, "Failed to remove file", err) .attach_context("path", filename.display().to_string()) .attach_context("tpe", tpe.to_string()) .attach_context("id", id.to_string()) - )?; + })?; Ok(()) } diff --git a/crates/core/src/backend/decrypt.rs b/crates/core/src/backend/decrypt.rs index dcb29ba6..4cb6842f 100644 --- a/crates/core/src/backend/decrypt.rs +++ b/crates/core/src/backend/decrypt.rs @@ -10,7 +10,7 @@ pub use zstd::compression_level_range; use crate::{ backend::{CryptBackendErrorKind, CryptBackendResult, FileType, ReadBackend, WriteBackend}, crypto::{hasher::hash, CryptoKey}, - error::RusticResult, + error::{ErrorKind, RusticError, RusticResult}, id::Id, repofile::{RepoFile, RepoId}, Progress, @@ -77,12 +77,20 @@ pub trait DecryptReadBackend: ReadBackend + Clone + 'static { ) -> RusticResult { let mut data = self.decrypt(data)?; if let Some(length) = uncompressed_length { - data = decode_all(&*data) - .map_err(CryptBackendErrorKind::DecodingZstdCompressedDataFailed) - .map_err(|_err| todo!("Error transition"))?; + data = decode_all(&*data).map_err(|err| { + RusticError::with_source( + ErrorKind::Compression, + "Failed to decode zstd compressed data. The data may be corrupted.", + err, + ) + })?; if data.len() != length.get() as usize { - return Err(CryptBackendErrorKind::LengthOfUncompressedDataDoesNotMatch) - .map_err(|_err| todo!("Error transition")); + return Err(RusticError::new( + ErrorKind::Compression, + "Length of uncompressed data does not match the given length. This is likely a bug. Please report this issue.", + ) + .attach_context("expected_length", length.get().to_string()) + .attach_context("actual_length", data.len().to_string())); } } Ok(data.into()) @@ -128,9 +136,15 @@ pub trait DecryptReadBackend: ReadBackend + Clone + 'static { /// If the file could not be read. fn get_file(&self, id: &Id) -> RusticResult { let data = self.read_encrypted_full(F::TYPE, id)?; - Ok(serde_json::from_slice(&data) - .map_err(CryptBackendErrorKind::DeserializingFromBytesOfJsonTextFailed) - .map_err(|_err| todo!("Error transition"))?) + let deserialized = serde_json::from_slice(&data).map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to deserialize file from JSON.", + err, + ) + })?; + + Ok(deserialized) } /// Streams all files. @@ -221,10 +235,7 @@ pub trait DecryptWriteBackend: WriteBackend + Clone + 'static { /// /// The hash of the written data. fn hash_write_full_uncompressed(&self, tpe: FileType, data: &[u8]) -> RusticResult { - let data = self - .key() - .encrypt_data(data) - .map_err(|_err| todo!("Error transition"))?; + let data = self.key().encrypt_data(data)?; let id = hash(&data); self.write_bytes(tpe, &id, false, data.into())?; Ok(id) @@ -245,9 +256,14 @@ pub trait DecryptWriteBackend: WriteBackend + Clone + 'static { /// /// [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`]: crate::error::CryptBackendErrorKind::SerializingToJsonByteVectorFailed fn save_file(&self, file: &F) -> RusticResult { - let data = serde_json::to_vec(file) - .map_err(CryptBackendErrorKind::SerializingToJsonByteVectorFailed) - .map_err(|_err| todo!("Error transition"))?; + let data = serde_json::to_vec(file).map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to serialize file to JSON. This is likely a bug. Please report this issue.", + err, + ) + })?; + self.hash_write_full(F::TYPE, &data) } @@ -267,9 +283,14 @@ pub trait DecryptWriteBackend: WriteBackend + Clone + 'static { /// /// [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`]: crate::error::CryptBackendErrorKind::SerializingToJsonByteVectorFailed fn save_file_uncompressed(&self, file: &F) -> RusticResult { - let data = serde_json::to_vec(file) - .map_err(CryptBackendErrorKind::SerializingToJsonByteVectorFailed) - .map_err(|_err| todo!("Error transition"))?; + let data = serde_json::to_vec(file).map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to serialize file to JSON. This is likely a bug. Please report this issue.", + err, + ) + })?; + self.hash_write_full_uncompressed(F::TYPE, &data) } @@ -384,12 +405,18 @@ impl DecryptBackend { let decrypted = self.decrypt(data)?; Ok(match decrypted.first() { Some(b'{' | b'[') => decrypted, // not compressed - Some(2) => decode_all(&decrypted[1..]) - .map_err(CryptBackendErrorKind::DecodingZstdCompressedDataFailed) - .map_err(|_err| todo!("Error transition"))?, // 2 indicates compressed data following + Some(2) => decode_all(&decrypted[1..]).map_err(|err| { + RusticError::with_source( + ErrorKind::Compression, + "Failed to decode zstd compressed data. The data may be corrupted.", + err, + ) + })?, // 2 indicates compressed data following _ => { - return Err(CryptBackendErrorKind::DecryptionNotSupportedForBackend) - .map_err(|_err| todo!("Error transition"))? + return Err(RusticError::new( + ErrorKind::Unsupported, + "Decryption not supported. The data is not in a supported format.", + ))? } }) } @@ -417,8 +444,12 @@ impl DecryptBackend { if self.extra_verify { let check_data = self.decrypt_file(data_encrypted)?; if data != check_data { - return Err(CryptBackendErrorKind::ExtraVerificationFailed) - .map_err(|_err| todo!("Error transition")); + return Err( + RusticError::new( + ErrorKind::Verification, + "Extra verification failed: After decrypting and decompressing the data changed! The data may be corrupted.\nPlease check the backend for corruption and try again. You can also try to run `rustic check --read-data` to check for corruption. This may take a long time.", + ) + ); } } Ok(()) @@ -459,11 +490,17 @@ impl DecryptBackend { if self.extra_verify { let data_check = self.read_encrypted_from_partial(data_encrypted, uncompressed_length)?; + if data != data_check { - return Err(CryptBackendErrorKind::ExtraVerificationFailed) - .map_err(|_err| todo!("Error transition")); + return Err( + RusticError::new( + ErrorKind::Verification, + "Extra verification failed: After decrypting and decompressing the data changed! The data may be corrupted.\nPlease check the backend for corruption and try again. You can also try to run `rustic check --read-data` to check for corruption. This may take a long time.", + ) + ); } } + Ok(()) } } @@ -494,20 +531,34 @@ impl DecryptWriteBackend for DecryptBackend { /// /// [`CryptBackendErrorKind::CopyEncodingDataFailed`]: crate::error::CryptBackendErrorKind::CopyEncodingDataFailed fn hash_write_full(&self, tpe: FileType, data: &[u8]) -> RusticResult { - let data_encrypted = self - .encrypt_file(data) - .map_err(|_err| todo!("Error transition"))?; + let data_encrypted = self.encrypt_file(data).map_err(|err| { + RusticError::with_source( + ErrorKind::Cryptography, + "Failed to encrypt data. The data may be corrupted.", + err, + ) + })?; + self.very_file(&data_encrypted, data)?; + let id = hash(&data_encrypted); + self.write_bytes(tpe, &id, false, data_encrypted.into())?; Ok(id) } fn process_data(&self, data: &[u8]) -> RusticResult<(Vec, u32, Option)> { - let (data_encrypted, data_len, uncompressed_length) = self - .encrypt_data(data) - .map_err(|_err| todo!("Error transition"))?; + let (data_encrypted, data_len, uncompressed_length) = + self.encrypt_data(data).map_err(|err| { + RusticError::with_source( + ErrorKind::Cryptography, + "Failed to encrypt data. The data may be corrupted.", + err, + ) + })?; + self.very_data(&data_encrypted, uncompressed_length, data)?; + Ok((data_encrypted, data_len, uncompressed_length)) } diff --git a/crates/core/src/backend/dry_run.rs b/crates/core/src/backend/dry_run.rs index 6fe337a0..ff65c82f 100644 --- a/crates/core/src/backend/dry_run.rs +++ b/crates/core/src/backend/dry_run.rs @@ -74,11 +74,10 @@ impl DecryptReadBackend for DryRunBackend { _ => { return Err( RusticError::new( - ErrorKind::Backend, + ErrorKind::Unsupported, "Decryption not supported. The data is not in a supported format.", )); } - } .into()) } diff --git a/crates/core/src/backend/ignore.rs b/crates/core/src/backend/ignore.rs index 8ebc3a38..87e3415a 100644 --- a/crates/core/src/backend/ignore.rs +++ b/crates/core/src/backend/ignore.rs @@ -27,7 +27,7 @@ use crate::{ node::{Metadata, Node, NodeType}, ReadSource, ReadSourceEntry, ReadSourceOpen, }, - error::RusticResult, + error::{ErrorKind, RusticError, RusticResult}, }; /// [`IgnoreErrorKind`] describes the errors that can be returned by a Ignore action in Backends @@ -194,48 +194,91 @@ impl LocalSource { let mut override_builder = OverrideBuilder::new(""); + // FIXME: Refactor this to a function to be reused + // This is the same of backend::ignore::Localsource::new + // https://github.com/rustic-rs/rustic_core/blob/db82ed21db158e66ef4f8f3e6ba8c8b52d2fd42a/crates/core/src/blob/tree.rs#L630 for g in &filter_opts.globs { _ = override_builder .add(g) - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| + RusticError::with_source( + ErrorKind::Internal, + "Failed to add glob pattern to override builder. This is a bug. Please report it.", + err, + ) + .attach_context("glob", g.to_string()) + )?; } for file in &filter_opts.glob_files { for line in std::fs::read_to_string(file) - .map_err(|err| IgnoreErrorKind::ErrorGlob { - file: file.into(), - source: err, - }) - .map_err(|_err| todo!("Error transition"))? + .map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to read string from glob file. This is a bug. Please report it.", + err, + ) + .attach_context("glob file", file.to_string()) + })? .lines() { _ = override_builder .add(line) - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| + RusticError::with_source( + ErrorKind::Internal, + "Failed to add glob pattern line to override builder. This is a bug. Please report it.", + err, + ) + .attach_context("glob pattern line", line.to_string()) + )?; } } _ = override_builder .case_insensitive(true) - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| + RusticError::with_source( + ErrorKind::Internal, + "Failed to set case insensitivity in override builder. This is a bug. Please report it.", + err, + ) + )?; for g in &filter_opts.iglobs { _ = override_builder .add(g) - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| + RusticError::with_source( + ErrorKind::Internal, + "Failed to add iglob pattern to override builder. This is a bug. Please report it.", + err, + ) + .attach_context("iglob", g.to_string()) + )?; } for file in &filter_opts.iglob_files { for line in std::fs::read_to_string(file) - .map_err(|err| IgnoreErrorKind::ErrorGlob { - file: file.into(), - source: err, - }) - .map_err(|_err| todo!("Error transition"))? + .map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to read string from iglob file. This is a bug. Please report it.", + err, + ) + .attach_context("iglob file", file.to_string()) + })? .lines() { _ = override_builder .add(line) - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| + RusticError::with_source( + ErrorKind::Internal, + "Failed to add iglob pattern line to override builder. This is a bug. Please report it.", + err, + ) + .attach_context("iglob pattern line", line.to_string()) + )?; } } @@ -255,7 +298,13 @@ impl LocalSource { .overrides( override_builder .build() - .map_err(|_err| todo!("Error transition"))?, + .map_err(|err| + RusticError::with_source( + ErrorKind::Internal, + "Failed to build matcher for a set of glob overrides. This is a bug. Please report it.", + err, + ) + )?, ); let exclude_if_present = filter_opts.exclude_if_present.clone(); @@ -368,7 +417,13 @@ impl Iterator for LocalSourceWalker { } .map(|e| { map_entry( - e.map_err(|_err| todo!("Error transition"))?, + e.map_err(|err| + RusticError::with_source( + ErrorKind::Internal, + "Failed to get next entry from walk iterator. This is a bug. Please report it.", + err, + ) + )?, self.save_opts.with_atime, self.save_opts.ignore_devid, ) diff --git a/crates/core/src/blob/tree.rs b/crates/core/src/blob/tree.rs index c3fbe4ff..176ab8b5 100644 --- a/crates/core/src/blob/tree.rs +++ b/crates/core/src/blob/tree.rs @@ -203,7 +203,7 @@ impl Tree { })? { let id = node .subtree - .ok_or_else(|| + .ok_or_else(|| RusticError::new( ErrorKind::Internal, "Node is not a directory. This is a bug. Please report it.", @@ -215,7 +215,7 @@ impl Tree { .nodes .into_iter() .find(|node| node.name() == p) - .ok_or_else(|| + .ok_or_else(|| RusticError::new( ErrorKind::Internal, "Node not found in tree. This is a bug. Please report it.", @@ -259,14 +259,13 @@ impl Tree { let node_idx = nodes.entry(node).or_insert(new_idx); Some(*node_idx) } else { - let id = node - .subtree - .ok_or_else(|| - RusticError::new( - ErrorKind::Internal, - "Subtree ID not found. This is a bug. Please report it.", - ).attach_context("node", path_comp[idx].to_string_lossy().to_string()) - )?; + let id = node.subtree.ok_or_else(|| { + RusticError::new( + ErrorKind::Internal, + "Subtree ID not found. This is a bug. Please report it.", + ) + .attach_context("node", path_comp[idx].to_string_lossy().to_string()) + })?; find_node_from_component( be, @@ -289,7 +288,7 @@ impl Tree { .components() .filter_map(|p| comp_to_osstr(p).transpose()) .collect::>() - .map_err(|err| + .map_err(|err| RusticError::with_source( ErrorKind::Internal, "Failed to convert Path component to OsString. This is a bug. Please report it.", @@ -367,14 +366,13 @@ impl Tree { for node in tree.nodes { let node_path = path.join(node.name()); if node.is_dir() { - let id = node - .subtree - .ok_or_else(|| - RusticError::new( - ErrorKind::Internal, - "Subtree ID not found. This is a bug. Please report it.", - ).attach_context("node", node.name().to_string_lossy().to_string()) - )?; + let id = node.subtree.ok_or_else(|| { + RusticError::new( + ErrorKind::Internal, + "Subtree ID not found. This is a bug. Please report it.", + ) + .attach_context("node", node.name().to_string_lossy().to_string()) + })?; result.append(&mut find_matching_nodes_recursive( be, index, id, &node_path, state, matches, @@ -624,10 +622,13 @@ where ) -> RusticResult { let mut override_builder = OverrideBuilder::new(""); + // FIXME: Refactor this to a function to be reused + // This is the same of backend::ignore::Localsource::new + // https://github.com/rustic-rs/rustic_core/blob/db82ed21db158e66ef4f8f3e6ba8c8b52d2fd42a/crates/core/src/backend/ignore.rs#L184 for g in &opts.glob { _ = override_builder .add(g) - .map_err(|err| + .map_err(|err| RusticError::with_source( ErrorKind::Internal, "Failed to add glob pattern to override builder. This is a bug. Please report it.", @@ -639,14 +640,14 @@ where for file in &opts.glob_file { for line in std::fs::read_to_string(file) - .map_err(|err| + .map_err(|err| { RusticError::with_source( ErrorKind::Internal, "Failed to read string from glob file. This is a bug. Please report it.", err, ) .attach_context("glob file", file.to_string()) - )? + })? .lines() { _ = override_builder @@ -659,7 +660,6 @@ where ) .attach_context("glob pattern line", line.to_string()) )?; - } } @@ -687,14 +687,14 @@ where for file in &opts.iglob_file { for line in std::fs::read_to_string(file) - .map_err(|err| + .map_err(|err| { RusticError::with_source( ErrorKind::Internal, "Failed to read string from iglob file. This is a bug. Please report it.", err, ) .attach_context("iglob file", file.to_string()) - )? + })? .lines() { _ = override_builder @@ -847,7 +847,7 @@ impl TreeStreamerOnce

{ for (count, id) in ids.into_iter().enumerate() { if !streamer .add_pending(PathBuf::new(), id, count) - .map_err(|err| + .map_err(|err| { RusticError::with_source( ErrorKind::Internal, "Failed to add tree ID to pending queue. This is a bug. Please report it.", @@ -855,7 +855,7 @@ impl TreeStreamerOnce

{ ) .attach_context("tree id", id.to_string()) .attach_context("count", count.to_string()) - )? + })? { streamer.p.inc(1); streamer.finished_ids += 1; diff --git a/crates/core/src/chunker.rs b/crates/core/src/chunker.rs index 60cc1096..9dc63559 100644 --- a/crates/core/src/chunker.rs +++ b/crates/core/src/chunker.rs @@ -210,7 +210,7 @@ pub fn random_poly() -> RusticResult { } Err(RusticError::new( - ErrorKind::Polynomial, + ErrorKind::Internal, "No suitable polynomial found, this should essentially never happen. Please try again, and then report this as a bug.", )) } diff --git a/crates/core/src/commands/restore.rs b/crates/core/src/commands/restore.rs index 68defcbd..65a11f5a 100644 --- a/crates/core/src/commands/restore.rs +++ b/crates/core/src/commands/restore.rs @@ -722,7 +722,7 @@ impl RestorePlan { let usize_length = usize::try_from(length).map_err(|err| { RusticError::with_source( - ErrorKind::Conversion, + ErrorKind::Internal, "Failed to convert the length to usize. Please try again.", err, ) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index c0793267..6797abfe 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -366,8 +366,6 @@ pub enum ErrorKind { Compression, /// Config Error Config, - /// Conversion Error - Conversion, /// Crypto Error Cryptography, /// External Command Error @@ -387,14 +385,14 @@ pub enum ErrorKind { Password, /// Permission Error Permission, - /// Polynomial Error - Polynomial, /// Processing Error Processing, /// Repository Error Repository, /// Something is not supported Unsupported, + /// Verification Error + Verification, /// Virtual File System Error Vfs, // /// The repository password is incorrect. Please try again. diff --git a/crates/core/src/repofile/keyfile.rs b/crates/core/src/repofile/keyfile.rs index b9c9416c..48e7e9a0 100644 --- a/crates/core/src/repofile/keyfile.rs +++ b/crates/core/src/repofile/keyfile.rs @@ -116,7 +116,7 @@ impl KeyFile { let params = Params::new( log_2(self.n).map_err(|err| { RusticError::with_source( - ErrorKind::Conversion, + ErrorKind::Internal, "Calculating log2 failed. Please check the key file and password.", err, ) diff --git a/crates/core/src/repofile/snapshotfile.rs b/crates/core/src/repofile/snapshotfile.rs index 788ab5f0..1509eeed 100644 --- a/crates/core/src/repofile/snapshotfile.rs +++ b/crates/core/src/repofile/snapshotfile.rs @@ -413,7 +413,7 @@ impl SnapshotFile { .to_str() .ok_or_else(|| { RusticError::new( - ErrorKind::Conversion, + ErrorKind::Internal, "Failed to convert hostname to string. The value must be a valid unicode string.", ) .attach_context("hostname", hostname.to_string_lossy().to_string()) @@ -428,7 +428,7 @@ impl SnapshotFile { (_, Some(duration)) => DeleteOption::After( time + Duration::from_std(*duration).map_err(|err| { RusticError::with_source( - ErrorKind::Conversion, + ErrorKind::Internal, "Failed to convert duration to std::time::Duration. Please make sure the value is a valid duration string.", err, ) diff --git a/crates/core/src/repository.rs b/crates/core/src/repository.rs index 736c7460..bfd26c84 100644 --- a/crates/core/src/repository.rs +++ b/crates/core/src/repository.rs @@ -266,7 +266,7 @@ pub fn read_password_from_reader(file: &mut impl BufRead) -> RusticResult RusticError { .attach_status(Status::Permanent) .attach_severity(Severity::Error) .attach_error_code("E001".into()) - .add_context("path", "/path/to/file") - .add_context("called", "used s3 backend") - .source(std::io::Error::new(std::io::ErrorKind::Other, "networking error").into()) - .backtrace(Backtrace::disabled()) + .attach_context("path", "/path/to/file") + .attach_context("called", "used s3 backend") + .attach_source(std::io::Error::new(std::io::ErrorKind::Other, "networking error").into()) } #[rstest] From 9ec62089171f6630652b70b78fe93670457e859d Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sat, 26 Oct 2024 21:24:34 +0200 Subject: [PATCH 046/129] More RusticErrors Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/archiver/file_archiver.rs | 4 +- crates/core/src/archiver/parent.rs | 5 +- crates/core/src/backend/local_destination.rs | 7 +- crates/core/src/blob/tree.rs | 6 +- crates/core/src/commands/backup.rs | 34 ++++-- crates/core/src/commands/check.rs | 112 ++++++++++--------- crates/core/src/commands/config.rs | 30 +++-- crates/core/src/commands/dump.rs | 5 +- crates/core/src/commands/key.rs | 12 +- crates/core/src/commands/merge.rs | 32 ++++-- crates/core/src/commands/prune.rs | 24 +++- crates/core/src/commands/repair/index.rs | 29 +++-- crates/core/src/commands/repair/snapshots.rs | 11 +- crates/core/src/commands/restore.rs | 23 +++- crates/core/src/repofile/configfile.rs | 2 +- crates/core/src/repofile/keyfile.rs | 12 +- crates/core/src/repofile/packfile.rs | 11 +- 17 files changed, 222 insertions(+), 137 deletions(-) diff --git a/crates/core/src/archiver/file_archiver.rs b/crates/core/src/archiver/file_archiver.rs index 71da1ca6..3fab2699 100644 --- a/crates/core/src/archiver/file_archiver.rs +++ b/crates/core/src/archiver/file_archiver.rs @@ -120,8 +120,8 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> FileArchiver<'a, BE, I> { (node, size) } else if node.node_type == NodeType::File { let r = open - .ok_or( - RusticError::new( + .ok_or_else( + || RusticError::new( ErrorKind::Internal, "Failed to unpack tree type optional. Option should contain a value, but contained `None`. This is a bug. Please report it.", ) diff --git a/crates/core/src/archiver/parent.rs b/crates/core/src/archiver/parent.rs index 87f8cf54..2f48a8ef 100644 --- a/crates/core/src/archiver/parent.rs +++ b/crates/core/src/archiver/parent.rs @@ -221,10 +221,7 @@ impl Parent { /// /// [`ArchiverErrorKind::TreeStackEmpty`]: crate::error::ArchiverErrorKind::TreeStackEmpty fn finish_dir(&mut self) -> ArchiverResult<()> { - let (tree, node_idx) = self - .stack - .pop() - .ok_or_else(|| ArchiverErrorKind::TreeStackEmpty)?; + let (tree, node_idx) = self.stack.pop().ok_or(ArchiverErrorKind::TreeStackEmpty)?; self.tree = tree; self.node_idx = node_idx; diff --git a/crates/core/src/backend/local_destination.rs b/crates/core/src/backend/local_destination.rs index 9b92e8e7..80363770 100644 --- a/crates/core/src/backend/local_destination.rs +++ b/crates/core/src/backend/local_destination.rs @@ -40,7 +40,7 @@ pub enum LocalDestinationErrorKind { DirectoryCreationFailed(std::io::Error), /// file `{0:?}` should have a parent FileDoesNotHaveParent(PathBuf), - /// DeviceID could not be converted to other type `{target}` of device `{device}`: `{source}` + /// `DeviceID` could not be converted to other type `{target}` of device `{device}`: `{source}` DeviceIdConversionFailed { target: String, device: u64, @@ -218,8 +218,7 @@ impl LocalDestination { /// /// [`LocalDestinationErrorKind::DirectoryRemovalFailed`]: crate::error::LocalDestinationErrorKind::DirectoryRemovalFailed pub(crate) fn remove_dir(&self, dirname: impl AsRef) -> LocalDestinationResult<()> { - Ok(fs::remove_dir_all(dirname) - .map_err(LocalDestinationErrorKind::DirectoryRemovalFailed)?) + fs::remove_dir_all(dirname).map_err(LocalDestinationErrorKind::DirectoryRemovalFailed) } /// Remove the given file (relative to the base path) @@ -241,7 +240,7 @@ impl LocalDestination { /// /// [`LocalDestinationErrorKind::FileRemovalFailed`]: crate::error::LocalDestinationErrorKind::FileRemovalFailed pub(crate) fn remove_file(&self, filename: impl AsRef) -> LocalDestinationResult<()> { - Ok(fs::remove_file(filename).map_err(LocalDestinationErrorKind::FileRemovalFailed)?) + fs::remove_file(filename).map_err(LocalDestinationErrorKind::FileRemovalFailed) } /// Create the given directory (relative to the base path) diff --git a/crates/core/src/blob/tree.rs b/crates/core/src/blob/tree.rs index 176ab8b5..38b77206 100644 --- a/crates/core/src/blob/tree.rs +++ b/crates/core/src/blob/tree.rs @@ -42,9 +42,9 @@ pub enum TreeErrorKind { PathNotFound(OsString), /// path should not contain current or parent dir ContainsCurrentOrParentDirectory, - /// serde_json couldn't serialize the tree: `{0:?}` + /// `serde_json` couldn't serialize the tree: `{0:?}` SerializingTreeFailed(serde_json::Error), - /// serde_json couldn't deserialize tree from bytes of JSON text: `{0:?}` + /// `serde_json` couldn't deserialize tree from bytes of JSON text: `{0:?}` DeserializingTreeFailed(serde_json::Error), /// slice is not UTF-8: `{0:?}` PathIsNotUtf8Conform(Utf8Error), @@ -460,7 +460,7 @@ pub(crate) fn comp_to_osstr(p: Component<'_>) -> TreeResult> { ), }, Component::Normal(p) => Some(p.to_os_string()), - _ => return Err(TreeErrorKind::ContainsCurrentOrParentDirectory.into()), + _ => return Err(TreeErrorKind::ContainsCurrentOrParentDirectory), }; Ok(s) } diff --git a/crates/core/src/commands/backup.rs b/crates/core/src/commands/backup.rs index bfac5ba9..20da508a 100644 --- a/crates/core/src/commands/backup.rs +++ b/crates/core/src/commands/backup.rs @@ -16,7 +16,7 @@ use crate::{ ignore::{LocalSource, LocalSourceFilterOptions, LocalSourceSaveOptions}, stdin::StdinSource, }, - error::RusticResult, + error::{ErrorKind, RusticError, RusticResult}, progress::ProgressBars, repofile::{ snapshotfile::{SnapshotGroup, SnapshotGroupCriterion, SnapshotId}, @@ -231,20 +231,34 @@ pub(crate) fn backup( .as_ref() .map(|p| -> RusticResult<_> { Ok(p.parse_dot() - .map_err(|_err| todo!("Error transition"))? + .map_err(|err| { + RusticError::with_source( + ErrorKind::Parsing, + "Failed to parse dotted path.", + err, + ) + .attach_context("path", p.display().to_string()) + })? .to_path_buf()) }) .transpose()?; match &as_path { - Some(p) => snap - .paths - .set_paths(&[p.clone()]) - .map_err(|_err| todo!("Error transition"))?, - None => snap - .paths - .set_paths(&backup_path) - .map_err(|_err| todo!("Error transition"))?, + Some(p) => snap.paths.set_paths(&[p.clone()]).map_err(|err| { + RusticError::with_source(ErrorKind::Internal, "Failed to set paths in snapshot.", err) + .attach_context("paths", p.display().to_string()) + })?, + None => snap.paths.set_paths(&backup_path).map_err(|err| { + RusticError::with_source(ErrorKind::Internal, "Failed to set paths in snapshot.", err) + .attach_context( + "paths", + backup_path + .iter() + .map(|p| p.display().to_string()) + .collect::>() + .join(","), + ) + })?, }; let (parent_id, parent) = opts.parent_opts.get_parent(repo, &snap, backup_stdin); diff --git a/crates/core/src/commands/check.rs b/crates/core/src/commands/check.rs index 6027d392..9833e709 100644 --- a/crates/core/src/commands/check.rs +++ b/crates/core/src/commands/check.rs @@ -35,89 +35,78 @@ use crate::{ }; #[non_exhaustive] -#[derive(thiserror::Error, Debug, displaydoc::Display)] -pub enum CheckCommandErrorKind { - /// error reading pack {id} : {source} - ErrorReadingPack { - id: PackId, - source: Box, - }, - /// cold file for hot file Type: {file_type:?}, Id: {id} does not exist +#[derive(Debug, displaydoc::Display)] +pub enum CheckIssueKind { + /// error reading pack `{id}` + ErrorReadingPack { id: PackId }, + /// cold file for hot file Type: `{file_type:?}`, Id: `{id}` does not exist NoColdFile { id: Id, file_type: FileType }, - /// Type: {file_type:?}, Id: {id}: hot size: {size_hot}, actual size: {size} + /// Type: `{file_type:?}`, Id: `{id}`: hot size: `{size_hot}`, actual size: `{size}` HotFileSizeMismatch { id: Id, file_type: FileType, size_hot: u32, size: u32, }, - /// hot file Type: {file_type:?}, Id: {id} is missing! + /// hot file Type: `{file_type:?}`, Id: {id} is missing! NoHotFile { id: Id, file_type: FileType }, - /// Error reading cached file Type: {file_type:?}, Id: {id} : {source} - ErrorReadingCache { - id: Id, - file_type: FileType, - source: Box, - }, - /// Error reading file Type: {file_type:?}, Id: {id} : {source} - ErrorReadingFile { - id: Id, - file_type: FileType, - source: Box, - }, - /// Cached file Type: {file_type:?}, Id: {id} is not identical to backend! + /// Error reading cached file Type: `{file_type:?}`, Id: `{id}` + ErrorReadingCache { id: Id, file_type: FileType }, + /// Error reading file Type: `{file_type:?}`, Id: `{id}` + ErrorReadingFile { id: Id, file_type: FileType }, + /// Cached file Type: `{file_type:?}`, Id: `{id}` is not identical to backend! CacheMismatch { id: Id, file_type: FileType }, - /// pack {id}: No time is set! Run prune to correct this! + /// pack `{id}`: No time is set! Run prune to correct this! PackTimeNotSet { id: PackId }, - /// pack {id}: blob {blob_id} blob type does not match: type: {blob_type:?}, expected: {expected:?} + /// pack `{id}`: blob `{blob_id}` blob type does not match: type: `{blob_type:?}`, expected: `{expected:?}` PackBlobTypesMismatch { id: PackId, blob_id: BlobId, blob_type: BlobType, expected: BlobType, }, - /// pack {id}: blob {blob_id} offset in index: {offset}, expected: {expected} + /// pack `{id}`: blob `{blob_id}` offset in index: `{offset}`, expected: `{expected}` PackBlobOffsetMismatch { id: PackId, blob_id: BlobId, offset: u32, expected: u32, }, - /// pack {id} not referenced in index. Can be a parallel backup job. To repair: 'rustic repair index'. + /// pack `{id}` not referenced in index. Can be a parallel backup job. To repair: 'rustic repair index'. PackNotReferenced { id: Id }, - /// pack {id}: size computed by index: {index_size}, actual size: {size}. To repair: 'rustic repair index'. + /// pack `{id}`: size computed by index: `{index_size}`, actual size: {size}. To repair: 'rustic repair index'. PackSizeMismatchIndex { id: Id, index_size: u32, size: u32 }, - /// pack {id} is referenced by the index but not present! To repair: 'rustic repair index'." + /// pack `{id}` is referenced by the index but not present! To repair: 'rustic repair index'." NoPack { id: PackId }, - /// file {file:?} doesn't have a content + /// file `{file:?}` doesn't have a content FileHasNoContent { file: PathBuf }, - /// file {file:?} blob {blob_num} has null ID + /// file `{file:?}` blob `{blob_num}` has null ID FileBlobHasNullId { file: PathBuf, blob_num: usize }, - /// file {file:?} blob {blob_id} is missing in index + /// file `{file:?}` blob `{blob_id}` is missing in index FileBlobNotInIndex { file: PathBuf, blob_id: Id }, - /// dir {dir:?} doesn't have a subtree + /// dir `{dir:?}` doesn't have a subtree NoSubTree { dir: PathBuf }, - /// "dir {dir:?} subtree has null ID + /// "dir `{dir:?}` subtree has null ID NullSubTree { dir: PathBuf }, - /// pack {id}: data size does not match expected size. Read: {size} bytes, expected: {expected} bytes + /// pack `{id}`: data size does not match expected size. Read: `{size}` bytes, expected: `{expected}` bytes PackSizeMismatch { id: PackId, size: usize, expected: usize, }, - /// pack {id}: Hash mismatch. Computed hash: {computed} + /// pack `{id}`: Hash mismatch. Computed hash: `{computed}` PackHashMismatch { id: PackId, computed: PackId }, - /// pack {id}: Header length in pack file doesn't match index. In pack: {length}, computed: {computed} + /// pack `{id}`: Header length in pack file doesn't match index. In pack: `{length}`, computed: `{computed}` PackHeaderLengthMismatch { id: PackId, length: u32, computed: u32, }, - /// pack {id}: Header from pack file does not match the index + /// pack `{id}`: Header from pack file does not match the index PackHeaderMismatchIndex { id: PackId }, - /// pack {id}, blob {blob_id}: Actual uncompressed length does not fit saved uncompressed length + /// pack `{id}`, blob `{blob_id}`: Actual uncompressed length does not fit saved uncompressed length PackBlobLengthMismatch { id: PackId, blob_id: BlobId }, - /// pack {id}, blob {blob_id}: Hash mismatch. Computed hash: {computed} + /// pack `{id}`, blob `{blob_id}`: Hash mismatch. Computed hash: `{computed}` PackBlobHashMismatch { id: PackId, blob_id: BlobId, @@ -125,7 +114,22 @@ pub enum CheckCommandErrorKind { }, } -pub(crate) type CheckCommandResult = Result<(), CheckCommandErrorKind>; +#[derive(Debug, Default)] +pub struct CheckIssues(Vec); + +impl std::ops::DerefMut for CheckIssues { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl std::ops::Deref for CheckIssues { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} #[derive(Clone, Copy, Debug, Default)] #[non_exhaustive] @@ -234,23 +238,25 @@ fn parse_n_m(now: NaiveDateTime, n_in: &str, m_in: &str) -> Result<(u32, u32), P } impl FromStr for ReadSubsetOption { - type Err = RusticError; + type Err = Box; fn from_str(s: &str) -> Result { let result = if s == "all" { Self::All } else if let Some(p) = s.strip_suffix('%') { // try to read percentage - Self::Percentage(p.parse().map_err(|err: ParseFloatError| { + let percentage = p.parse().map_err(|err: ParseFloatError| { RusticError::with_source( ErrorKind::Parsing, "Error parsing percentage for ReadSubset option. Did you forget the '%'?", err, ) .attach_context("value", p.to_string()) - })?) + })?; + + Self::Percentage(percentage) } else if let Some((n, m)) = s.split_once('/') { let now = Local::now().naive_local(); - Self::IdSubSet(parse_n_m(now, n, m).map_err( + let subset = parse_n_m(now, n, m).map_err( |err| RusticError::with_source( ErrorKind::Parsing, @@ -258,12 +264,13 @@ impl FromStr for ReadSubsetOption { err ) .attach_context("value", s) - .attach_context("n/m", format!("{}/{}", n, m)) + .attach_context("n/m", format!("{n}/{m}")) .attach_context("now", now.to_string()) - )?) + )?; + + Self::IdSubSet(subset) } else { - Self::Size( - ByteSize::from_str(s) + let byte_size = ByteSize::from_str(s) .map_err(|err| { RusticError::with_source( ErrorKind::Parsing, @@ -272,9 +279,11 @@ impl FromStr for ReadSubsetOption { ) .attach_context("value", s) })? - .as_u64(), - ) + .as_u64(); + + Self::Size(byte_size) }; + Ok(result) } } @@ -403,6 +412,7 @@ pub(crate) fn check_repository( }); p.finish(); } + Ok(()) } diff --git a/crates/core/src/commands/config.rs b/crates/core/src/commands/config.rs index 29122138..9badf7c3 100644 --- a/crates/core/src/commands/config.rs +++ b/crates/core/src/commands/config.rs @@ -7,7 +7,7 @@ use derive_setters::Setters; use crate::{ backend::decrypt::{DecryptBackend, DecryptWriteBackend}, crypto::CryptoKey, - error::RusticResult, + error::{ErrorKind, RusticError, RusticResult}, repofile::ConfigFile, repository::{Open, Repository}, }; @@ -27,9 +27,9 @@ pub enum ConfigCommandErrorKind { CannotDowngrade(u32, u32), /// Size is too large: `{0}` SizeTooLarge(ByteSize), - /// min_packsize_tolerate_percent must be <= 100 + /// `min_packsize_tolerate_percent` must be <= 100 MinPackSizeTolerateWrong, - /// max_packsize_tolerate_percent must be >= 100 or 0" + /// `max_packsize_tolerate_percent` must be >= 100 or 0" MaxPackSizeTolerateWrong, } @@ -268,8 +268,7 @@ impl ConfigOptions { config.treepack_size = Some( size.as_u64() .try_into() - .map_err(|_| ConfigCommandErrorKind::SizeTooLarge(size)) - .map_err(|_err| todo!("Error transition"))?, + .map_err(|err| construct_size_too_large_error(err, size))?, ); } if let Some(factor) = self.set_treepack_growfactor { @@ -279,8 +278,7 @@ impl ConfigOptions { config.treepack_size_limit = Some( size.as_u64() .try_into() - .map_err(|_| ConfigCommandErrorKind::SizeTooLarge(size)) - .map_err(|_err| todo!("Error transition"))?, + .map_err(|err| construct_size_too_large_error(err, size))?, ); } @@ -288,8 +286,7 @@ impl ConfigOptions { config.datapack_size = Some( size.as_u64() .try_into() - .map_err(|_| ConfigCommandErrorKind::SizeTooLarge(size)) - .map_err(|_err| todo!("Error transition"))?, + .map_err(|err| construct_size_too_large_error(err, size))?, ); } if let Some(factor) = self.set_datapack_growfactor { @@ -299,8 +296,7 @@ impl ConfigOptions { config.datapack_size_limit = Some( size.as_u64() .try_into() - .map_err(|_| ConfigCommandErrorKind::SizeTooLarge(size)) - .map_err(|_err| todo!("Error transition"))?, + .map_err(|err| construct_size_too_large_error(err, size))?, ); } @@ -325,3 +321,15 @@ impl ConfigOptions { Ok(()) } } + +fn construct_size_too_large_error( + err: std::num::TryFromIntError, + size: ByteSize, +) -> Box { + RusticError::with_source( + ErrorKind::Internal, + "Failed to convert ByteSize to u64. Size is too large.", + err, + ) + .attach_context("size", size.to_string()) +} diff --git a/crates/core/src/commands/dump.rs b/crates/core/src/commands/dump.rs index 104ab6ef..4d2507aa 100644 --- a/crates/core/src/commands/dump.rs +++ b/crates/core/src/commands/dump.rs @@ -41,8 +41,9 @@ pub(crate) fn dump( for id in node.content.as_ref().unwrap() { let data = repo.get_blob_cached(&BlobId::from(**id), BlobType::Data)?; - w.write_all(&data) - .map_err(|_err| todo!("Error transition"))?; + w.write_all(&data).map_err(|err| { + RusticError::with_source(ErrorKind::Io, "Failed to write data to writer.", err) + })?; } Ok(()) } diff --git a/crates/core/src/commands/key.rs b/crates/core/src/commands/key.rs index ce2aba59..dfd54508 100644 --- a/crates/core/src/commands/key.rs +++ b/crates/core/src/commands/key.rs @@ -4,7 +4,7 @@ use derive_setters::Setters; use crate::{ backend::{decrypt::DecryptWriteBackend, FileType, WriteBackend}, crypto::{aespoly1305::Key, hasher::hash}, - error::RusticResult, + error::{ErrorKind, RusticError, RusticResult}, repofile::{KeyFile, KeyId}, repository::{Open, Repository}, }; @@ -112,10 +112,14 @@ pub(crate) fn add_key_to_repo( let ko = opts.clone(); let keyfile = KeyFile::generate(key, &pass, ko.hostname, ko.username, ko.with_created)?; - let data = serde_json::to_vec(&keyfile).map_err(|_err| todo!("Error transition"))?; + let data = serde_json::to_vec(&keyfile).map_err(|err| { + RusticError::with_source(ErrorKind::Io, "Failed to serialize keyfile to JSON.", err) + })?; + let id = KeyId::from(hash(&data)); + repo.be - .write_bytes(FileType::Key, &id, false, data.into()) - .map_err(|_err| todo!("Error transition"))?; + .write_bytes(FileType::Key, &id, false, data.into())?; + Ok(id) } diff --git a/crates/core/src/commands/merge.rs b/crates/core/src/commands/merge.rs index c8cbc193..a50766ef 100644 --- a/crates/core/src/commands/merge.rs +++ b/crates/core/src/commands/merge.rs @@ -11,7 +11,7 @@ use crate::{ tree::{self, Tree, TreeId}, BlobId, BlobType, }, - error::RusticResult, + error::{ErrorKind, RusticError, RusticResult}, index::{indexer::Indexer, ReadIndex}, progress::{Progress, ProgressBars}, repofile::{PathList, SnapshotFile, SnapshotSummary}, @@ -44,9 +44,10 @@ pub(crate) fn merge_snapshots( .collect::() .merge(); - snap.paths - .set_paths(&paths.paths()) - .map_err(|_err| todo!("Error transition"))?; + snap.paths.set_paths(&paths.paths()).map_err(|err| { + RusticError::with_source(ErrorKind::Internal, "Failed to set paths in snapshot.", err) + .attach_context("paths", paths.to_string()) + })?; // set snapshot time to time of latest snapshot to be merged snap.time = snapshots @@ -60,9 +61,9 @@ pub(crate) fn merge_snapshots( let trees: Vec = snapshots.iter().map(|sn| sn.tree).collect(); snap.tree = merge_trees(repo, &trees, cmp, &mut summary)?; - summary - .finalize(now) - .map_err(|_err| todo!("Error transition"))?; + summary.finalize(now).map_err(|err| { + RusticError::with_source(ErrorKind::Internal, "Failed to finalize summary.", err) + })?; snap.summary = Some(summary); snap.id = repo.dbe().save_file(&snap)?.into(); @@ -108,12 +109,25 @@ pub(crate) fn merge_trees( repo.config(), index.total_size(BlobType::Tree), )?; + let save = |tree: Tree| -> RusticResult<_> { - let (chunk, new_id) = tree.serialize().map_err(|_err| todo!("Error transition"))?; - let size = u64::try_from(chunk.len()).map_err(|_err| todo!("Error transition"))?; + let (chunk, new_id) = tree.serialize().map_err(|err| { + RusticError::with_source(ErrorKind::Internal, "Failed to serialize tree.", err) + })?; + + let size = u64::try_from(chunk.len()).map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to convert chunk length to u64.", + err, + ) + .attach_context("chunk", chunk.len().to_string()) + })?; + if !index.has_tree(&new_id) { packer.add(chunk.into(), BlobId::from(*new_id))?; } + Ok((new_id, size)) }; diff --git a/crates/core/src/commands/prune.rs b/crates/core/src/commands/prune.rs index d00d19e4..1e6738d6 100644 --- a/crates/core/src/commands/prune.rs +++ b/crates/core/src/commands/prune.rs @@ -30,7 +30,7 @@ use crate::{ tree::TreeStreamerOnce, BlobId, BlobType, BlobTypeMap, Initialize, }, - error::{RusticError, RusticResult}, + error::{ErrorKind, RusticError, RusticResult}, index::{ binarysorted::{IndexCollector, IndexType}, indexer::Indexer, @@ -42,7 +42,6 @@ use crate::{ SnapshotFile, SnapshotId, }, repository::{Open, Repository}, - ErrorKind, }; pub(super) mod constants { @@ -738,14 +737,30 @@ impl PrunePlan { .unwrap_or_else(|| repo.config().is_hot == Some(true)); let pack_sizer = total_size.map(|tpe, size| PackSizer::from_config(repo.config(), tpe, size)); + pruner.decide_packs( - Duration::from_std(*opts.keep_pack).map_err(|_err| todo!("Error transition"))?, - Duration::from_std(*opts.keep_delete).map_err(|_err| todo!("Error transition"))?, + Duration::from_std(*opts.keep_pack).map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to convert keep_pack duration to std::time::Duration.", + err, + ) + .attach_context("keep_pack", opts.keep_pack.to_string()) + })?, + Duration::from_std(*opts.keep_delete).map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to convert keep_delete duration to std::time::Duration.", + err, + ) + .attach_context("keep_delete", opts.keep_delete.to_string()) + })?, repack_cacheable_only, opts.repack_uncompressed, opts.repack_all, &pack_sizer, )?; + pruner.decide_repack( &opts.max_repack, &opts.max_unused, @@ -753,6 +768,7 @@ impl PrunePlan { opts.no_resize, &pack_sizer, ); + pruner.check_existing_packs()?; pruner.filter_index_files(opts.instant_delete); diff --git a/crates/core/src/commands/repair/index.rs b/crates/core/src/commands/repair/index.rs index f5ba72be..55b21fad 100644 --- a/crates/core/src/commands/repair/index.rs +++ b/crates/core/src/commands/repair/index.rs @@ -78,12 +78,14 @@ pub(crate) fn repair_index( let indexer = Indexer::new(be.clone()).into_shared(); let p = repo.pb.progress_counter("reading pack headers"); - p.set_length( - pack_read_header - .len() - .try_into() - .map_err(|_err| todo!("Error transition"))?, - ); + + p.set_length(pack_read_header.len().try_into().map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to convert pack_read_header length to u64.", + err, + ) + })?); for (id, size_hint, packsize) in pack_read_header { debug!("reading pack {id}..."); match PackHeader::from_file(be, id, size_hint, packsize) { @@ -190,12 +192,15 @@ pub(crate) fn index_checked_from_collector( repo.warm_up_wait(pack_read_header.iter().map(|(id, _, _)| *id))?; let p = repo.pb.progress_counter("reading pack headers"); - p.set_length( - pack_read_header - .len() - .try_into() - .map_err(|_err| todo!("Error transition"))?, - ); + + p.set_length(pack_read_header.len().try_into().map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to convert pack_read_header length to u64.", + err, + ) + })?); + let index_packs: Vec<_> = pack_read_header .into_iter() .map(|(id, size_hint, packsize)| { diff --git a/crates/core/src/commands/repair/snapshots.rs b/crates/core/src/commands/repair/snapshots.rs index 6b594f99..4514f764 100644 --- a/crates/core/src/commands/repair/snapshots.rs +++ b/crates/core/src/commands/repair/snapshots.rs @@ -283,13 +283,22 @@ pub(crate) fn repair_tree( (Some(id), Changed::None) => Ok((Changed::None, id)), (_, c) => { // the tree has been changed => save it - let (chunk, new_id) = tree.serialize().map_err(|_err| todo!("Error transition"))?; + let (chunk, new_id) = tree.serialize().map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to serialize tree. This is likely a bug, please report it.", + err, + ) + })?; + if !index.has_tree(&new_id) && !dry_run { packer.add(chunk.into(), BlobId::from(*new_id))?; } + if let Some(id) = id { _ = state.replaced.insert(id, (c, new_id)); } + Ok((c, new_id)) } } diff --git a/crates/core/src/commands/restore.rs b/crates/core/src/commands/restore.rs index 65a11f5a..9129f975 100644 --- a/crates/core/src/commands/restore.rs +++ b/crates/core/src/commands/restore.rs @@ -23,11 +23,10 @@ use crate::{ node::{Node, NodeType}, FileType, ReadBackend, }, - error::RusticResult, + error::{ErrorKind, RusticError, RusticResult}, progress::{Progress, ProgressBars}, repofile::packfile::PackId, repository::{IndexedFull, IndexedTree, Open, Repository}, - ErrorKind, RusticError, }; pub(crate) mod constants { @@ -677,7 +676,14 @@ impl RestorePlan { .as_ref() .map(std::fs::File::metadata) .transpose() - .map_err(|_err| todo!("Error transition"))? + .map_err(|err| + RusticError::with_source( + ErrorKind::Io, + "Failed to get the metadata of the file. Please check the path and try again.", + err + ) + .attach_context("path", name.display().to_string()) + )? { if meta.len() == 0 { // Empty file exists @@ -691,9 +697,16 @@ impl RestorePlan { .as_ref() .map(std::fs::File::metadata) .transpose() - .map_err(|_err| todo!("Error transition"))? + .map_err(|err| + RusticError::with_source( + ErrorKind::Io, + "Failed to get the metadata of the file. Please check the path and try again.", + err + ) + .attach_context("path", name.display().to_string()) + )? { - // TODO: This is the same logic as in backend/ignore.rs => consollidate! + // TODO: This is the same logic as in backend/ignore.rs => consolidate! let mtime = meta .modified() .ok() diff --git a/crates/core/src/repofile/configfile.rs b/crates/core/src/repofile/configfile.rs index df8c9cec..81febdea 100644 --- a/crates/core/src/repofile/configfile.rs +++ b/crates/core/src/repofile/configfile.rs @@ -162,7 +162,7 @@ impl ConfigFile { ErrorKind::Parsing, "Parsing u64 from hex failed for polynomial, the value must be a valid hexadecimal string.", err) - .attach_context("polynomial",&self.chunker_polynomial.to_string())) + .attach_context("polynomial",self.chunker_polynomial.to_string())) ?; Ok(chunker_poly) diff --git a/crates/core/src/repofile/keyfile.rs b/crates/core/src/repofile/keyfile.rs index 48e7e9a0..23833851 100644 --- a/crates/core/src/repofile/keyfile.rs +++ b/crates/core/src/repofile/keyfile.rs @@ -15,18 +15,18 @@ use crate::{ #[derive(thiserror::Error, Debug, displaydoc::Display)] #[non_exhaustive] pub enum KeyFileErrorKind { - /// listing KeyFiles failed + /// listing `KeyFiles` failed ListingKeyFilesFailed, - /// couldn't get KeyFile from backend + /// couldn't get `KeyFile` from backend CouldNotGetKeyFileFromBackend, - /// serde_json couldn't deserialize the data for the key: `{key_id:?}` : `{source}` + /// `serde_json` couldn't deserialize the data for the key: `{key_id:?}` : `{source}` DeserializingFromSliceForKeyIdFailed { /// The id of the key key_id: KeyId, /// The error that occurred source: serde_json::Error, }, - /// serde_json couldn't serialize the data into a JSON byte vector: `{0:?}` + /// `serde_json` couldn't serialize the data into a JSON byte vector: `{0:?}` CouldNotSerializeAsJsonByteVector(serde_json::Error), /// output length is invalid: `{0:?}` OutputLengthInvalid(scrypt::errors::InvalidOutputLen), @@ -34,7 +34,7 @@ pub enum KeyFileErrorKind { InvalidSCryptParameters(scrypt::errors::InvalidParams), /// deserializing master key from slice failed: `{source}` DeserializingMasterKeyFromSliceFailed { source: serde_json::Error }, - /// conversion from {from} to {to} failed for {x} : {source} + /// conversion from `{from}` to `{to}` failed for `{x}` : `{source}` ConversionFailed { from: &'static str, to: &'static str, @@ -284,7 +284,7 @@ impl KeyFile { serde_json::from_slice(&data) .map_err( |err| KeyFileErrorKind::DeserializingFromSliceForKeyIdFailed { - key_id: id.clone(), + key_id: *id, source: err, }, ) diff --git a/crates/core/src/repofile/packfile.rs b/crates/core/src/repofile/packfile.rs index 26ec384b..40f173f1 100644 --- a/crates/core/src/repofile/packfile.rs +++ b/crates/core/src/repofile/packfile.rs @@ -25,7 +25,7 @@ pub enum PackFileErrorKind { HeaderLengthTooLarge { size_real: u32, pack_size: u32 }, /// decrypting from binary failed BinaryDecryptionFailed, - /// Partial read of PackFile failed + /// Partial read of `PackFile` failed PartialReadOfPackfileFailed, /// writing Bytes failed WritingBytesFailed, @@ -79,10 +79,7 @@ impl PackHeaderLength { /// [`PackFileErrorKind::ReadingBinaryRepresentationFailed`]: crate::error::PackFileErrorKind::ReadingBinaryRepresentationFailed pub(crate) fn from_binary(data: &[u8]) -> PackFileResult { let mut reader = Cursor::new(data); - Ok( - Self::read(&mut reader) - .map_err(PackFileErrorKind::ReadingBinaryRepresentationFailed)?, - ) + Self::read(&mut reader).map_err(PackFileErrorKind::ReadingBinaryRepresentationFailed) } /// Generate the binary representation of the pack header length @@ -251,9 +248,7 @@ impl PackHeader { let blob = match HeaderEntry::read(&mut reader) { Ok(entry) => entry.into_blob(offset), Err(err) if err.is_eof() => break, - Err(err) => { - return Err(PackFileErrorKind::ReadingBinaryRepresentationFailed(err).into()) - } + Err(err) => return Err(PackFileErrorKind::ReadingBinaryRepresentationFailed(err)), }; offset += blob.length; blobs.push(blob); From 1c672c2b896e331adcff462d22577afaadb99790 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sat, 26 Oct 2024 21:31:47 +0200 Subject: [PATCH 047/129] fix error related clippy lints Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/error.rs | 24 +++++++++--------- crates/core/src/id.rs | 2 +- crates/core/src/index.rs | 2 +- crates/core/src/repofile/snapshotfile.rs | 8 +++--- crates/core/src/repository.rs | 16 ++++++------ crates/core/src/repository/command_input.rs | 28 ++++++++++++++++----- crates/core/src/vfs.rs | 10 ++++---- 7 files changed, 53 insertions(+), 37 deletions(-) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 6797abfe..af99875a 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -215,7 +215,7 @@ impl RusticError { impl RusticError { /// Attach what kind the error is. pub fn attach_kind(self, value: impl Into) -> Box { - Box::new(RusticError { + Box::new(Self { kind: value.into(), ..self }) @@ -226,7 +226,7 @@ impl RusticError { self, value: impl Into>, ) -> Box { - Box::new(RusticError { + Box::new(Self { source: Some(value.into()), ..self }) @@ -234,7 +234,7 @@ impl RusticError { /// Attach the error message with guidance. pub fn attach_guidance(self, value: impl Into) -> Box { - Box::new(RusticError { + Box::new(Self { guidance: value.into(), ..self }) @@ -244,7 +244,7 @@ impl RusticError { /// Attach context to the error. pub fn attach_context(mut self, key: &'static str, value: impl Into) -> Box { let mut context = self.context.to_vec(); - context.push((key, value.into().into())); + context.push((key, value.into())); self.context = context.into_boxed_slice(); Box::new(self) } @@ -256,7 +256,7 @@ impl RusticError { /// This should not be used in most cases, as it will overwrite any existing contexts. /// Rather use `attach_context` for multiple contexts. pub fn overwrite_context(self, value: impl Into>) -> Box { - Box::new(RusticError { + Box::new(Self { context: value.into(), ..self }) @@ -264,7 +264,7 @@ impl RusticError { /// Attach the URL of the documentation for the error. pub fn attach_docs_url(self, value: impl Into) -> Box { - Box::new(RusticError { + Box::new(Self { docs_url: Some(value.into()), ..self }) @@ -272,7 +272,7 @@ impl RusticError { /// Attach an error code. pub fn attach_error_code(self, value: impl Into) -> Box { - Box::new(RusticError { + Box::new(Self { error_code: Some(value.into()), ..self }) @@ -280,7 +280,7 @@ impl RusticError { /// Attach the URL of the issue tracker for opening a new issue. pub fn attach_new_issue_url(self, value: impl Into) -> Box { - Box::new(RusticError { + Box::new(Self { new_issue_url: Some(value.into()), ..self }) @@ -288,7 +288,7 @@ impl RusticError { /// Attach the URL of an already existing issue that is related to this error. pub fn attach_existing_issue_url(self, value: impl Into) -> Box { - Box::new(RusticError { + Box::new(Self { existing_issue_url: Some(value.into()), ..self }) @@ -296,7 +296,7 @@ impl RusticError { /// Attach the severity of the error. pub fn attach_severity(self, value: impl Into) -> Box { - Box::new(RusticError { + Box::new(Self { severity: Some(value.into()), ..self }) @@ -304,7 +304,7 @@ impl RusticError { /// Attach the status of the error. pub fn attach_status(self, value: impl Into) -> Box { - Box::new(RusticError { + Box::new(Self { status: Some(value.into()), ..self }) @@ -314,7 +314,7 @@ impl RusticError { /// /// This should not be used in most cases, as the backtrace is automatically captured. pub fn attach_backtrace_manually(self, value: impl Into) -> Box { - Box::new(RusticError { + Box::new(Self { backtrace: Some(value.into()), ..self }) diff --git a/crates/core/src/id.rs b/crates/core/src/id.rs index 11c7ad26..32d5b549 100644 --- a/crates/core/src/id.rs +++ b/crates/core/src/id.rs @@ -45,7 +45,7 @@ macro_rules! define_new_id_struct { pub struct $a($crate::Id); impl $a { - /// impl into_inner + /// impl `into_inner` #[must_use] pub fn into_inner(self) -> $crate::Id { self.0 diff --git a/crates/core/src/index.rs b/crates/core/src/index.rs index f74ce7ac..1a1e1000 100644 --- a/crates/core/src/index.rs +++ b/crates/core/src/index.rs @@ -27,7 +27,7 @@ pub enum IndexErrorKind { BlobInIndexNotFound, /// failed to get a blob from the backend GettingBlobIndexEntryFromBackendFailed, - /// saving IndexFile failed + /// saving `IndexFile` failed SavingIndexFileFailed { /// the error that occurred source: CryptBackendErrorKind, diff --git a/crates/core/src/repofile/snapshotfile.rs b/crates/core/src/repofile/snapshotfile.rs index 1509eeed..24b49dd1 100644 --- a/crates/core/src/repofile/snapshotfile.rs +++ b/crates/core/src/repofile/snapshotfile.rs @@ -45,11 +45,11 @@ pub enum SnapshotFileErrorKind { ValueNotAllowed(String), /// datetime out of range: `{0:?}` OutOfRange(OutOfRangeError), - /// getting the SnapshotFile from the backend failed + /// getting the `SnapshotFile` from the backend failed GettingSnapshotFileFailed, - /// getting the SnapshotFile by ID failed + /// getting the `SnapshotFile` by ID failed GettingSnapshotFileByIdFailed, - /// unpacking SnapshotFile result failed + /// unpacking `SnapshotFile` result failed UnpackingSnapshotFileResultFailed, /// collecting IDs failed: `{0:?}` FindingIdsFailed(Vec), @@ -1011,7 +1011,7 @@ impl FromStr for SnapshotGroupCriterion { "paths" => crit.paths = true, "tags" => crit.tags = true, "" => continue, - v => return Err(SnapshotFileErrorKind::ValueNotAllowed(v.into()).into()), + v => return Err(SnapshotFileErrorKind::ValueNotAllowed(v.into())), } } Ok(crit) diff --git a/crates/core/src/repository.rs b/crates/core/src/repository.rs index bfd26c84..60e20040 100644 --- a/crates/core/src/repository.rs +++ b/crates/core/src/repository.rs @@ -487,7 +487,7 @@ impl Repository { /// [`RusticErrorKind::ListingRepositoryConfigFileFailed`]: crate::error::RusticErrorKind::ListingRepositoryConfigFileFailed /// [`RusticErrorKind::MoreThanOneRepositoryConfig`]: crate::error::RusticErrorKind::MoreThanOneRepositoryConfig pub fn open(self) -> RusticResult> { - let password = self.password()?.ok_or({ + let password = self.password()?.ok_or_else(|| { RusticError::new( ErrorKind::Password, "No password given, or Password was empty. Please specify a valid password.", @@ -521,13 +521,13 @@ impl Repository { /// [`RusticErrorKind::ListingRepositoryConfigFileFailed`]: crate::error::RusticErrorKind::ListingRepositoryConfigFileFailed /// [`RusticErrorKind::MoreThanOneRepositoryConfig`]: crate::error::RusticErrorKind::MoreThanOneRepositoryConfig pub fn open_with_password(self, password: &str) -> RusticResult> { - let config_id = self.config_id()?.ok_or( + let config_id = self.config_id()?.ok_or_else(|| { RusticError::new( ErrorKind::Config, "No repository config file found. Please check the repository.", ) - .attach_context("name", self.name.clone()), - )?; + .attach_context("name", self.name.clone()) + })?; if let Some(be_hot) = &self.be_hot { let mut keys = self.be.list_with_size(FileType::Key)?; @@ -585,13 +585,13 @@ impl Repository { key_opts: &KeyOptions, config_opts: &ConfigOptions, ) -> RusticResult> { - let password = self.password()?.ok_or( + let password = self.password()?.ok_or_else(|| { RusticError::new( ErrorKind::Password, "No password given, or Password was empty. Please specify a valid password.", ) - .attach_context("name", self.name.clone()), - )?; + .attach_context("name", self.name.clone()) + })?; self.init_with_password(&password, key_opts, config_opts) } @@ -630,7 +630,7 @@ impl Repository { ErrorKind::Config, "Config file already exists. Please check the repository.", ) - .attach_context("name", self.name.clone())); + .attach_context("name", self.name)); } let (key, config) = commands::init::init(&self, pass, key_opts, config_opts)?; diff --git a/crates/core/src/repository/command_input.rs b/crates/core/src/repository/command_input.rs index 7b5f2965..0bb8e742 100644 --- a/crates/core/src/repository/command_input.rs +++ b/crates/core/src/repository/command_input.rs @@ -16,25 +16,43 @@ use crate::error::RusticResult; pub enum CommandInputErrorKind { /// Command execution failed: {context}:{what} : {source} CommandExecutionFailed { + /// The context in which the command was called context: String, + + /// The action that was performed what: String, + + /// The source of the error source: std::io::Error, }, /// Command error status: {context}:{what} : {status} CommandErrorStatus { + /// The context in which the command was called context: String, + + /// The action that was performed what: String, + + /// The exit status of the command status: ExitStatus, }, /// Splitting arguments failed: {arguments} : {source} SplittingArgumentsFailed { + /// The arguments that were tried to be split arguments: String, + + /// The source of the error source: shell_words::ParseError, }, /// Process execution failed: {command:?} : {path:?} : {source} ProcessExecutionFailed { + /// The command that was tried to be executed command: CommandInput, + + /// The path in which the command was tried to be executed path: std::path::PathBuf, + + /// The source of the error source: std::io::Error, }, } @@ -253,10 +271,8 @@ impl OnFailure { /// helper to split arguments // TODO: Maybe use special parser (winsplit?) for windows? fn split(s: &str) -> CommandInputResult> { - Ok( - shell_words::split(s).map_err(|err| CommandInputErrorKind::SplittingArgumentsFailed { - arguments: s.to_string(), - source: err, - })?, - ) + shell_words::split(s).map_err(|err| CommandInputErrorKind::SplittingArgumentsFailed { + arguments: s.to_string(), + source: err, + }) } diff --git a/crates/core/src/vfs.rs b/crates/core/src/vfs.rs index 64cd9c05..a0bec285 100644 --- a/crates/core/src/vfs.rs +++ b/crates/core/src/vfs.rs @@ -35,7 +35,7 @@ pub enum VfsErrorKind { DirectoryExistsAsNonVirtual, /// Only normal paths allowed OnlyNormalPathsAreAllowed, - /// Name `{0:?}`` doesn't exist + /// Name `{0:?}` doesn't exist NameDoesNotExist(OsString), } @@ -111,7 +111,7 @@ impl VfsTree { let mut tree = self; let mut components = path.components(); let Some(Component::Normal(last)) = components.next_back() else { - return Err(VfsErrorKind::OnlyNormalPathsAreAllowed.into()); + return Err(VfsErrorKind::OnlyNormalPathsAreAllowed); }; for comp in components { @@ -123,14 +123,14 @@ impl VfsTree { .or_insert(Self::VirtualTree(BTreeMap::new())); } _ => { - return Err(VfsErrorKind::DirectoryExistsAsNonVirtual.into()); + return Err(VfsErrorKind::DirectoryExistsAsNonVirtual); } } } } let Self::VirtualTree(virtual_tree) = tree else { - return Err(VfsErrorKind::DirectoryExistsAsNonVirtual.into()); + return Err(VfsErrorKind::DirectoryExistsAsNonVirtual); }; _ = virtual_tree.insert(last.to_os_string(), new_tree); @@ -164,7 +164,7 @@ impl VfsTree { if let Some(new_tree) = virtual_tree.get(name) { tree = new_tree; } else { - return Err(VfsErrorKind::NameDoesNotExist(name.to_os_string()).into()); + return Err(VfsErrorKind::NameDoesNotExist(name.to_os_string())); }; } None => { From db6ddef16d3f2afd841eef11a657ff2365078c4a Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sat, 26 Oct 2024 22:36:45 +0200 Subject: [PATCH 048/129] update error Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/tests/errors.rs | 14 ++++++++------ .../core/tests/snapshots/errors__error_debug.snap | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/core/tests/errors.rs b/crates/core/tests/errors.rs index 2c55d128..3da3dc09 100644 --- a/crates/core/tests/errors.rs +++ b/crates/core/tests/errors.rs @@ -1,28 +1,30 @@ use rstest::{fixture, rstest}; -use std::backtrace::Backtrace; use rustic_core::{ErrorKind, RusticError, Severity, Status}; #[fixture] -fn error() -> RusticError { +fn error() -> Box { RusticError::new( ErrorKind::Io, "A file could not be read, make sure the file is existing and readable by the system.", ) .attach_status(Status::Permanent) .attach_severity(Severity::Error) - .attach_error_code("E001".into()) + .attach_error_code("E001") .attach_context("path", "/path/to/file") .attach_context("called", "used s3 backend") - .attach_source(std::io::Error::new(std::io::ErrorKind::Other, "networking error").into()) + .attach_source(std::io::Error::new( + std::io::ErrorKind::Other, + "networking error", + )) } #[rstest] -fn test_error_display(error: RusticError) { +fn test_error_display(error: Box) { insta::assert_snapshot!(error); } #[rstest] -fn test_error_debug(error: RusticError) { +fn test_error_debug(error: Box) { insta::assert_debug_snapshot!(error); } diff --git a/crates/core/tests/snapshots/errors__error_debug.snap b/crates/core/tests/snapshots/errors__error_debug.snap index d6c7f45f..af164a92 100644 --- a/crates/core/tests/snapshots/errors__error_debug.snap +++ b/crates/core/tests/snapshots/errors__error_debug.snap @@ -22,7 +22,7 @@ RusticError { ), ], docs_url: None, - code: Some( + error_code: Some( "E001", ), new_issue_url: None, From 85c3c9bf6073b860cad9a7dd81630462335f535f Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sat, 26 Oct 2024 23:23:24 +0200 Subject: [PATCH 049/129] More RusticErrors Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/local.rs | 45 +++---- crates/backend/src/opendal.rs | 209 ++++++++++++++++++++++++------ crates/backend/src/rclone.rs | 8 +- crates/backend/src/rest.rs | 4 +- crates/core/src/commands/check.rs | 4 +- 5 files changed, 196 insertions(+), 74 deletions(-) diff --git a/crates/backend/src/local.rs b/crates/backend/src/local.rs index ea8a75b4..18b2bf81 100644 --- a/crates/backend/src/local.rs +++ b/crates/backend/src/local.rs @@ -12,8 +12,8 @@ use log::{debug, trace, warn}; use walkdir::WalkDir; use rustic_core::{ - CommandInput, CommandInputErrorKind, ErrorKind, FileType, Id, ReadBackend, RusticError, - RusticResult, WriteBackend, ALL_FILE_TYPES, + CommandInput, ErrorKind, FileType, Id, ReadBackend, RusticError, RusticResult, WriteBackend, + ALL_FILE_TYPES, }; /// [`LocalBackendErrorKind`] describes the errors that can be returned by an action on the filesystem in Backends @@ -24,11 +24,11 @@ pub enum LocalBackendErrorKind { DirectoryCreationFailed(std::io::Error), /// querying metadata failed: `{0:?}` QueryingMetadataFailed(std::io::Error), - /// querying WalkDir metadata failed: `{0:?}` + /// querying `WalkDir` metadata failed: `{0:?}` QueryingWalkDirMetadataFailed(walkdir::Error), /// execution of command failed: `{0:?}` CommandExecutionFailed(std::io::Error), - /// command was not successful for filename {file_name}, type {file_type}, id {id}: {status} + /// command was not successful for filename `{file_name}`, type `{file_type}`, id `{id}`: `{status}` CommandNotSuccessful { /// File name file_name: String, @@ -41,10 +41,10 @@ pub enum LocalBackendErrorKind { }, /// error building automaton `{0:?}` FromAhoCorasick(aho_corasick::BuildError), - /// {0:?} + /// `{0:?}` #[error(transparent)] FromTryIntError(TryFromIntError), - /// {0:?} + /// `{0:?}` #[error(transparent)] FromWalkdirError(walkdir::Error), /// removing file failed: `{0:?}` @@ -84,6 +84,10 @@ impl LocalBackend { /// * `path` - The base path of the backend /// * `options` - Additional options for the backend /// + /// # Errors + /// + /// + /// /// # Returns /// /// A new [`LocalBackend`] instance @@ -180,18 +184,15 @@ impl LocalBackend { debug!("calling {actual_command}..."); - let command: CommandInput = - actual_command - .parse() - .map_err(|err: CommandInputErrorKind| { - RusticError::with_source( - ErrorKind::Parsing, - "Failed to parse command input. This is a bug. Please report it.", - err, - ) - .attach_context("command", actual_command) - .attach_context("replacement", replace_with.join(", ")) - })?; + let command: CommandInput = actual_command.parse().map_err(|err| { + RusticError::with_source( + ErrorKind::Parsing, + "Failed to parse command input. This is a bug. Please report it.", + err, + ) + .attach_context("command", actual_command) + .attach_context("replacement", replace_with.join(", ")) + })?; let status = Command::new(command.command()) .args(command.args()) @@ -289,7 +290,7 @@ impl ReadBackend for LocalBackend { )? .len() .try_into() - .map_err(|err: TryFromIntError| + .map_err(|err| RusticError::with_source( ErrorKind::Backend, "Failed to convert file length to u32. This is a bug. Please report it.", @@ -322,7 +323,7 @@ impl ReadBackend for LocalBackend { ? .len() .try_into() - .map_err(|err: TryFromIntError| + .map_err(|err| RusticError::with_source( ErrorKind::Backend, "Failed to convert file length to u32. This is a bug. Please report it.", @@ -404,7 +405,7 @@ impl ReadBackend for LocalBackend { let mut vec = vec![ 0; - length.try_into().map_err(|err: TryFromIntError| { + length.try_into().map_err(|err| { RusticError::with_source( ErrorKind::Backend, "Failed to convert length to u64. This is a bug. Please report it.", @@ -508,7 +509,7 @@ impl WriteBackend for LocalBackend { .attach_context("path", filename.to_string_lossy()) })?; - file.set_len(buf.len().try_into().map_err(|err: TryFromIntError| { + file.set_len(buf.len().try_into().map_err(|err| { RusticError::with_source( ErrorKind::Backend, "Failed to convert length to u64. This is a bug. Please report it.", diff --git a/crates/backend/src/opendal.rs b/crates/backend/src/opendal.rs index 1fd9b811..9fcd8648 100644 --- a/crates/backend/src/opendal.rs +++ b/crates/backend/src/opendal.rs @@ -1,5 +1,5 @@ /// `OpenDAL` backend for rustic. -use std::{collections::HashMap, num::TryFromIntError, str::FromStr, sync::OnceLock}; +use std::{collections::HashMap, str::FromStr, sync::OnceLock}; use bytes::Bytes; use bytesize::ByteSize; @@ -47,7 +47,7 @@ pub struct Throttle { } impl FromStr for Throttle { - type Err = RusticError; + type Err = Box; fn from_str(s: &str) -> Result { let mut values = s .split(',') @@ -62,13 +62,13 @@ impl FromStr for Throttle { }) }) .map(|b| -> RusticResult { - Ok(b?.as_u64().try_into().map_err(|err: TryFromIntError| { + b?.as_u64().try_into().map_err(|err| { RusticError::with_source( ErrorKind::Parsing, "Converting ByteSize to u32 failed", err, ) - })?) + }) }); let bandwidth = values .next() @@ -103,22 +103,44 @@ impl OpenDALBackend { let max_retries = match options.get("retry").map(String::as_str) { Some("false" | "off") => 0, None | Some("default") => constants::DEFAULT_RETRY, - Some(value) => usize::from_str(value).map_err(|_err| todo!("Error transition"))?, + Some(value) => usize::from_str(value).map_err(|err| { + RusticError::with_source(ErrorKind::Parsing, "Parsing retry value failed", err) + .attach_context("value", value.to_string()) + })?, }; let connections = options .get("connections") - .map(|c| usize::from_str(c)) - .transpose() - .map_err(|_err| todo!("Error transition"))?; + .map(|c| { + usize::from_str(c).map_err(|err| { + RusticError::with_source( + ErrorKind::Parsing, + "Parsing connections value failed", + err, + ) + .attach_context("value", c.to_string()) + }) + }) + .transpose()?; let throttle = options .get("throttle") .map(|t| Throttle::from_str(t)) .transpose()?; - let schema = Scheme::from_str(path.as_ref()).map_err(|_err| todo!("Error transition"))?; + let schema = Scheme::from_str(path.as_ref()).map_err(|err| { + RusticError::with_source(ErrorKind::Parsing, "Parsing scheme from path failed", err) + .attach_context("path", path.as_ref().to_string()) + })?; let mut operator = Operator::via_iter(schema, options) - .map_err(|_err| todo!("Error transition"))? + .map_err(|err| { + RusticError::with_source( + ErrorKind::Backend, + "Creating Operator failed. Please check the given schema and options.", + err, + ) + .attach_context("path", path.as_ref().to_string()) + .attach_context("schema", schema.to_string()) + })? .layer(RetryLayer::new().with_max_times(max_retries).with_jitter()); if let Some(Throttle { bandwidth, burst }) = throttle { @@ -132,7 +154,13 @@ impl OpenDALBackend { let _guard = runtime().enter(); let operator = operator .layer(LoggingLayer::default()) - .layer(BlockingLayer::create().map_err(|_err| todo!("Error transition"))?) + .layer(BlockingLayer::create().map_err(|err| { + RusticError::with_source( + ErrorKind::Backend, + "Creating BlockingLayer failed. This is a bug. Please report it.", + err, + ) + })?) .blocking(); Ok(Self { operator }) @@ -186,11 +214,13 @@ impl ReadBackend for OpenDALBackend { trace!("listing tpe: {tpe:?}"); if tpe == FileType::Config { return Ok( - if self - .operator - .is_exist("config") - .map_err(|_err| todo!("Error transition"))? - { + if self.operator.is_exist("config").map_err(|err| { + RusticError::with_source( + ErrorKind::Backend, + "Path `config` does not exist. This is a bug. Please report it.", + err, + ) + })? { vec![Id::default()] } else { Vec::new() @@ -198,12 +228,22 @@ impl ReadBackend for OpenDALBackend { ); } + let path = &(tpe.dirname().to_string() + "/"); + Ok(self .operator - .list_with(&(tpe.dirname().to_string() + "/")) + .list_with(path) .recursive(true) .call() - .map_err(|_err| todo!("Error transition"))? + .map_err(|err| { + RusticError::with_source( + ErrorKind::Backend, + "Listing all files failed in the backend. Please check if the given path is correct.", + err, + ) + .attach_context("path", path) + .attach_context("type", tpe.to_string()) + })? .into_iter() .filter(|e| e.metadata().is_file()) .filter_map(|e| e.name().parse().ok()) @@ -222,23 +262,43 @@ impl ReadBackend for OpenDALBackend { return match self.operator.stat("config") { Ok(entry) => Ok(vec![( Id::default(), - entry - .content_length() - .try_into() - .map_err(|_err| todo!("Error transition"))?, + entry.content_length().try_into().map_err(|err| { + RusticError::with_source( + ErrorKind::Parsing, + "Parsing content length failed", + err, + ) + .attach_context("content length", entry.content_length().to_string()) + })?, )]), Err(err) if err.kind() == opendal::ErrorKind::NotFound => Ok(Vec::new()), - Err(err) => Err(err).map_err(|_err| todo!("Error transition")), + Err(err) => Err(err).map_err(|err| + RusticError::with_source( + ErrorKind::Backend, + "Getting Metadata of `config` failed in the backend. Please check if the `config` exists.", + err, + ) + .attach_context("type", tpe.to_string()) + ), }; } + let path = tpe.dirname().to_string() + "/"; Ok(self .operator - .list_with(&(tpe.dirname().to_string() + "/")) + .list_with(&path) .recursive(true) .metakey(Metakey::ContentLength) .call() - .map_err(|_err| todo!("Error transition"))? + .map_err(|err| + RusticError::with_source( + ErrorKind::Backend, + "Listing all files in directory and their sizes failed in the backend. Please check if the given path is correct.", + err, + ) + .attach_context("path", path) + .attach_context("type", tpe.to_string()) + )? .into_iter() .filter(|e| e.metadata().is_file()) .map(|e| -> RusticResult<(Id, u32)> { @@ -247,7 +307,14 @@ impl ReadBackend for OpenDALBackend { e.metadata() .content_length() .try_into() - .map_err(|_err| todo!("Error transition"))?, + .map_err(|err| + RusticError::with_source( + ErrorKind::Parsing, + "Parsing content length failed", + err, + ) + .attach_context("content length", e.metadata().content_length().to_string()) + )?, )) }) .filter_map(RusticResult::ok) @@ -257,10 +324,20 @@ impl ReadBackend for OpenDALBackend { fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult { trace!("reading tpe: {tpe:?}, id: {id}"); + let path = self.path(tpe, id); Ok(self .operator - .read(&self.path(tpe, id)) - .map_err(|_err| todo!("Error transition"))? + .read(&path) + .map_err(|err| + RusticError::with_source( + ErrorKind::Backend, + "Reading file failed in the backend. Please check if the given path is correct.", + err, + ) + .attach_context("path", path) + .attach_context("type", tpe.to_string()) + .attach_context("id", id.to_string()) + )? .to_bytes()) } @@ -274,12 +351,25 @@ impl ReadBackend for OpenDALBackend { ) -> RusticResult { trace!("reading tpe: {tpe:?}, id: {id}, offset: {offset}, length: {length}"); let range = u64::from(offset)..u64::from(offset + length); + let path = self.path(tpe, id); + Ok(self .operator - .read_with(&self.path(tpe, id)) + .read_with(&path) .range(range) .call() - .map_err(|_err| todo!("Error transition"))? + .map_err(|err| + RusticError::with_source( + ErrorKind::Backend, + "Partially reading file failed in the backend. Please check if the given path is correct.", + err, + ) + .attach_context("path", path) + .attach_context("type", tpe.to_string()) + .attach_context("id", id.to_string()) + .attach_context("offset", offset.to_string()) + .attach_context("length", length.to_string()) + )? .to_bytes()) } } @@ -290,23 +380,41 @@ impl WriteBackend for OpenDALBackend { trace!("creating repo at {:?}", self.location()); for tpe in ALL_FILE_TYPES { + let path = tpe.dirname().to_string() + "/"; self.operator - .create_dir(&(tpe.dirname().to_string() + "/")) - .map_err(|_err| todo!("Error transition"))?; + .create_dir(&path) + .map_err(|err| + RusticError::with_source( + ErrorKind::Backend, + "Creating directory failed in the backend. Please check if the given path is correct.", + err, + ) + .attach_context("location", self.location()) + .attach_context("path", path) + .attach_context("type", tpe.to_string()) + )?; } // creating 256 dirs can be slow on remote backends, hence we parallelize it. (0u8..=255) .into_par_iter() .try_for_each(|i| { - self.operator.create_dir( - &(UnixPathBuf::from("data") + let path = UnixPathBuf::from("data") .join(hex::encode([i])) .to_string_lossy() .to_string() - + "/"), + + "/"; + + self.operator.create_dir(&path) + .map_err(|err| + RusticError::with_source( + ErrorKind::Backend, + "Creating directory failed in the backend. Please check if the given path is correct.", + err, ) - }) - .map_err(|_err| todo!("Error transition"))?; + .attach_context("location", self.location()) + .attach_context("path", path) + ) + })?; Ok(()) } @@ -328,9 +436,17 @@ impl WriteBackend for OpenDALBackend { ) -> RusticResult<()> { trace!("writing tpe: {:?}, id: {}", &tpe, &id); let filename = self.path(tpe, id); - self.operator - .write(&filename, buf) - .map_err(|_err| todo!("Error transition"))?; + self.operator.write(&filename, buf).map_err(|err| { + RusticError::with_source( + ErrorKind::Backend, + "Writing file failed in the backend. Please check if the given path is correct.", + err, + ) + .attach_context("path", filename) + .attach_context("type", tpe.to_string()) + .attach_context("id", id.to_string()) + })?; + Ok(()) } @@ -344,9 +460,16 @@ impl WriteBackend for OpenDALBackend { fn remove(&self, tpe: FileType, id: &Id, _cacheable: bool) -> RusticResult<()> { trace!("removing tpe: {:?}, id: {}", &tpe, &id); let filename = self.path(tpe, id); - self.operator - .delete(&filename) - .map_err(|_err| todo!("Error transition"))?; + self.operator.delete(&filename).map_err(|err| { + RusticError::with_source( + ErrorKind::Backend, + "Deleting file failed in the backend. Please check if the given path is correct.", + err, + ) + .attach_context("path", filename) + .attach_context("type", tpe.to_string()) + .attach_context("id", id.to_string()) + })?; Ok(()) } } diff --git a/crates/backend/src/rclone.rs b/crates/backend/src/rclone.rs index 74bcd21c..4bd3f002 100644 --- a/crates/backend/src/rclone.rs +++ b/crates/backend/src/rclone.rs @@ -2,7 +2,6 @@ use std::{ collections::HashMap, io::{BufRead, BufReader}, process::{Child, Command, Stdio}, - str::ParseBoolError, thread::JoinHandle, }; @@ -18,8 +17,7 @@ use semver::{BuildMetadata, Prerelease, Version, VersionReq}; use crate::rest::RestBackend; use rustic_core::{ - CommandInput, CommandInputErrorKind, ErrorKind, FileType, Id, ReadBackend, RusticError, - RusticResult, WriteBackend, + CommandInput, ErrorKind, FileType, Id, ReadBackend, RusticError, RusticResult, WriteBackend, }; pub(super) mod constants { @@ -159,7 +157,7 @@ impl RcloneBackend { let rclone_command = options.get("rclone-command"); let use_password = options .get("use-password") - .map(|v| v.parse().map_err(|err: ParseBoolError| + .map(|v| v.parse().map_err(|err| RusticError::with_source( ErrorKind::Parsing, "Expected 'use-password' to be a boolean, but it was not. Please check the configuration file.", @@ -193,7 +191,7 @@ impl RcloneBackend { rclone_command.push(' '); rclone_command.push_str(url.as_ref()); let rclone_command: CommandInput = rclone_command.parse().map_err( - |err: CommandInputErrorKind| RusticError::with_source( + |err| RusticError::with_source( ErrorKind::Parsing, "Expected rclone command to be valid, but it was not. Please check the configuration file.", err diff --git a/crates/backend/src/rest.rs b/crates/backend/src/rest.rs index 63598edb..2b3aaceb 100644 --- a/crates/backend/src/rest.rs +++ b/crates/backend/src/rest.rs @@ -431,7 +431,7 @@ impl ReadBackend for RestBackend { } } -fn construct_backoff_error(err: backoff::Error) -> RusticError { +fn construct_backoff_error(err: backoff::Error) -> Box { RusticError::with_source( ErrorKind::Backend, "Backoff failed, please check the logs for more information.", @@ -444,7 +444,7 @@ fn construct_join_url_error( tpe: FileType, id: &Id, self_url: &Url, -) -> RusticError { +) -> Box { RusticError::with_source(ErrorKind::Parsing, "Joining URL failed", err) .attach_context("url", self_url.as_str()) .attach_context("tpe", tpe.to_string()) diff --git a/crates/core/src/commands/check.rs b/crates/core/src/commands/check.rs index 9833e709..95c99e75 100644 --- a/crates/core/src/commands/check.rs +++ b/crates/core/src/commands/check.rs @@ -2,7 +2,7 @@ use std::{ collections::{BTreeSet, HashMap}, fmt::Debug, - num::{ParseFloatError, ParseIntError}, + num::ParseIntError, path::PathBuf, str::FromStr, }; @@ -244,7 +244,7 @@ impl FromStr for ReadSubsetOption { Self::All } else if let Some(p) = s.strip_suffix('%') { // try to read percentage - let percentage = p.parse().map_err(|err: ParseFloatError| { + let percentage = p.parse().map_err(|err| { RusticError::with_source( ErrorKind::Parsing, "Error parsing percentage for ReadSubset option. Did you forget the '%'?", From c45ede876193a2e8e0dd636cabc188c1d694436f Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 00:32:03 +0200 Subject: [PATCH 050/129] More RusticErrors from tests Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/repofile/snapshotfile.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/core/src/repofile/snapshotfile.rs b/crates/core/src/repofile/snapshotfile.rs index 24b49dd1..d50c7eb3 100644 --- a/crates/core/src/repofile/snapshotfile.rs +++ b/crates/core/src/repofile/snapshotfile.rs @@ -566,10 +566,15 @@ impl SnapshotFile { } } } + p.finish(); - latest - .ok_or_else(|| SnapshotFileErrorKind::NoSnapshotsFound) - .map_err(|_err| todo!("Error transition")) + + latest.ok_or_else(|| { + RusticError::new( + ErrorKind::Repository, + "No snapshots found. Please make sure there are snapshots in the repository.", + ) + }) } /// Get a [`SnapshotFile`] from the backend by (part of the) id From f34fb99ddcf04761bf18b29656309dc7f0ad2b50 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 01:05:16 +0200 Subject: [PATCH 051/129] more error methods Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/error.rs | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index af99875a..2ad6070b 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -178,7 +178,7 @@ impl RusticError { /// Checks if the error is due to an incorrect password pub fn is_incorrect_password(&self) -> bool { - matches!(self.kind, ErrorKind::Password) + matches!(self.error_code.as_deref(), Some("C002")) } /// Creates a new error from a given error. @@ -214,7 +214,7 @@ impl RusticError { // to be added and is not generated by `derive_setters`. impl RusticError { /// Attach what kind the error is. - pub fn attach_kind(self, value: impl Into) -> Box { + pub fn overwrite_kind(self, value: impl Into) -> Box { Box::new(Self { kind: value.into(), ..self @@ -233,13 +233,31 @@ impl RusticError { } /// Attach the error message with guidance. - pub fn attach_guidance(self, value: impl Into) -> Box { + pub fn overwrite_guidance(self, value: impl Into) -> Box { Box::new(Self { guidance: value.into(), ..self }) } + /// Append a newline to the guidance message. + /// This is useful for adding additional information to the guidance. + pub fn append_guidance_line(self, value: impl Into) -> Box { + Box::new(Self { + guidance: format!("{}\n{}", self.guidance, value.into()).into(), + ..self + }) + } + + /// Prepend a newline to the guidance message. + /// This is useful for adding additional information to the guidance. + pub fn prepend_guidance_line(self, value: impl Into) -> Box { + Box::new(Self { + guidance: format!("{}\n{}", value.into(), self.guidance).into(), + ..self + }) + } + // IMPORTANT: This is manually implemented to allow for multiple contexts to be added. /// Attach context to the error. pub fn attach_context(mut self, key: &'static str, value: impl Into) -> Box { @@ -310,10 +328,10 @@ impl RusticError { }) } - /// Attach a backtrace of the error. + /// Overwrite the backtrace of the error. /// /// This should not be used in most cases, as the backtrace is automatically captured. - pub fn attach_backtrace_manually(self, value: impl Into) -> Box { + pub fn overwrite_backtrace(self, value: impl Into) -> Box { Box::new(Self { backtrace: Some(value.into()), ..self From 686257f14ec277afe45d86e1b54f5393002e5073 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 01:05:42 +0200 Subject: [PATCH 052/129] rename test cases for keys to make more clear their intent Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/tests/keys.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/core/tests/keys.rs b/crates/core/tests/keys.rs index 8e99ff32..45608ad5 100644 --- a/crates/core/tests/keys.rs +++ b/crates/core/tests/keys.rs @@ -10,7 +10,7 @@ use sha2::{Digest, Sha256}; #[case("test", true)] #[case("test2", true)] #[case("wrong", false)] -fn working_keys(#[case] password: &str, #[case] should_work: bool) -> Result<()> { +fn test_working_keys_passes(#[case] password: &str, #[case] should_work: bool) -> Result<()> { let be = InMemoryBackend::new(); add_to_be(&be, FileType::Config, "tests/fixtures/config")?; add_to_be(&be, FileType::Key, "tests/fixtures/key1")?; @@ -29,7 +29,7 @@ fn working_keys(#[case] password: &str, #[case] should_work: bool) -> Result<()> #[test] // using an invalid keyfile: Here the scrypt params are not valid -fn failing_key() -> Result<()> { +fn test_keys_failing_passes() -> Result<()> { let be = InMemoryBackend::new(); add_to_be(&be, FileType::Config, "tests/fixtures/config")?; add_to_be(&be, FileType::Key, "tests/fixtures/key-failing")?; From cda7b7174d31a33135dcfae0e02e03ddfb81abc4 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 01:07:43 +0200 Subject: [PATCH 053/129] Add error code and overwrite guidance to password incorrect error Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/repository.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/core/src/repository.rs b/crates/core/src/repository.rs index 60e20040..10c4e970 100644 --- a/crates/core/src/repository.rs +++ b/crates/core/src/repository.rs @@ -543,7 +543,11 @@ impl Repository { } } - let key = find_key_in_backend(&self.be, &password, None)?; + let key = find_key_in_backend(&self.be, &password, None).map_err(|err| { + err.attach_error_code("C002") + .overwrite_kind(ErrorKind::Password) + .overwrite_guidance("The password that has been entered, seems to be incorrect. Please check the password.") + })?; info!("repository {}: password is correct.", self.name); From 6c3ec5ba12d40e9bc9ed69d790fdb1f6b3522bd6 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 01:15:14 +0200 Subject: [PATCH 054/129] Fix tests for incorrect passwort and key files Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/repofile/keyfile.rs | 6 +++--- crates/core/src/repository.rs | 6 +----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/crates/core/src/repofile/keyfile.rs b/crates/core/src/repofile/keyfile.rs index 23833851..f1d00fa0 100644 --- a/crates/core/src/repofile/keyfile.rs +++ b/crates/core/src/repofile/keyfile.rs @@ -431,8 +431,8 @@ pub(crate) fn find_key_in_backend( } Err(RusticError::new( - ErrorKind::Key, - "No suitable key found for the given password. Please check your password and try again.", - )) + ErrorKind::Password, + "The password that has been entered, seems to be incorrect. No suitable key found for the given password. Please check your password and try again.", + ).attach_error_code("C002")) } } diff --git a/crates/core/src/repository.rs b/crates/core/src/repository.rs index 10c4e970..60e20040 100644 --- a/crates/core/src/repository.rs +++ b/crates/core/src/repository.rs @@ -543,11 +543,7 @@ impl Repository { } } - let key = find_key_in_backend(&self.be, &password, None).map_err(|err| { - err.attach_error_code("C002") - .overwrite_kind(ErrorKind::Password) - .overwrite_guidance("The password that has been entered, seems to be incorrect. Please check the password.") - })?; + let key = find_key_in_backend(&self.be, &password, None)?; info!("repository {}: password is correct.", self.name); From 5038925af13cdba7340cc2906d26959a0e6633a7 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 01:26:41 +0200 Subject: [PATCH 055/129] fix imports Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/archiver.rs | 4 ++-- crates/core/src/blob/packer.rs | 3 +-- crates/core/src/blob/tree.rs | 3 +-- crates/core/src/chunker.rs | 3 +-- crates/core/src/commands/cat.rs | 3 +-- crates/core/src/commands/dump.rs | 3 +-- crates/core/src/crypto/aespoly1305.rs | 5 ++++- crates/core/src/index.rs | 3 +-- crates/core/src/repofile/configfile.rs | 8 ++++++-- crates/core/src/repofile/keyfile.rs | 4 ++-- crates/core/src/repofile/packfile.rs | 3 +-- crates/core/src/repofile/snapshotfile.rs | 4 ++-- crates/core/src/vfs.rs | 3 +-- 13 files changed, 24 insertions(+), 25 deletions(-) diff --git a/crates/core/src/archiver.rs b/crates/core/src/archiver.rs index 19a6cb1f..627f2116 100644 --- a/crates/core/src/archiver.rs +++ b/crates/core/src/archiver.rs @@ -16,13 +16,13 @@ use crate::{ }, backend::{decrypt::DecryptFullBackend, ReadSource, ReadSourceEntry}, blob::BlobType, - error::RusticResult, + error::{ErrorKind, RusticError, RusticResult}, index::{ indexer::{Indexer, SharedIndexer}, ReadGlobalIndex, }, repofile::{configfile::ConfigFile, snapshotfile::SnapshotFile}, - ErrorKind, Progress, RusticError, + Progress, }; /// [`ArchiverErrorKind`] describes the errors that can be returned from the archiver diff --git a/crates/core/src/blob/packer.rs b/crates/core/src/blob/packer.rs index 090c07e1..bb0eb4ea 100644 --- a/crates/core/src/blob/packer.rs +++ b/crates/core/src/blob/packer.rs @@ -18,7 +18,7 @@ use crate::{ }, blob::{BlobId, BlobType}, crypto::{hasher::hash, CryptoKey}, - error::RusticResult, + error::{ErrorKind, RusticError, RusticResult}, index::indexer::SharedIndexer, repofile::{ configfile::ConfigFile, @@ -26,7 +26,6 @@ use crate::{ packfile::{PackHeaderLength, PackHeaderRef, PackId}, snapshotfile::SnapshotSummary, }, - ErrorKind, RusticError, }; /// [`PackerErrorKind`] describes the errors that can be returned for a Packer diff --git a/crates/core/src/blob/tree.rs b/crates/core/src/blob/tree.rs index 38b77206..5449bece 100644 --- a/crates/core/src/blob/tree.rs +++ b/crates/core/src/blob/tree.rs @@ -22,12 +22,11 @@ use crate::{ }, blob::BlobType, crypto::hasher::hash, - error::RusticResult, + error::{ErrorKind, RusticError, RusticResult}, impl_blobid, index::ReadGlobalIndex, progress::Progress, repofile::snapshotfile::SnapshotSummary, - ErrorKind, RusticError, }; /// [`TreeErrorKind`] describes the errors that can come up dealing with Trees diff --git a/crates/core/src/chunker.rs b/crates/core/src/chunker.rs index 9dc63559..82f775a3 100644 --- a/crates/core/src/chunker.rs +++ b/crates/core/src/chunker.rs @@ -7,8 +7,7 @@ use crate::{ polynom::{Polynom, Polynom64}, rolling_hash::{Rabin64, RollingHash64}, }, - error::RusticResult, - ErrorKind, RusticError, + error::{ErrorKind, RusticError, RusticResult}, }; /// [`PolynomialErrorKind`] describes the errors that can happen while dealing with Polynomials diff --git a/crates/core/src/commands/cat.rs b/crates/core/src/commands/cat.rs index 1c10e765..05453d3f 100644 --- a/crates/core/src/commands/cat.rs +++ b/crates/core/src/commands/cat.rs @@ -5,12 +5,11 @@ use bytes::Bytes; use crate::{ backend::{decrypt::DecryptReadBackend, FileType, FindInBackend}, blob::{tree::Tree, BlobId, BlobType}, - error::RusticResult, + error::{ErrorKind, RusticError, RusticResult}, index::ReadIndex, progress::ProgressBars, repofile::SnapshotFile, repository::{IndexedFull, IndexedTree, Open, Repository}, - ErrorKind, RusticError, }; /// Prints the contents of a file. diff --git a/crates/core/src/commands/dump.rs b/crates/core/src/commands/dump.rs index 4d2507aa..0aa75cac 100644 --- a/crates/core/src/commands/dump.rs +++ b/crates/core/src/commands/dump.rs @@ -3,9 +3,8 @@ use std::io::Write; use crate::{ backend::node::{Node, NodeType}, blob::{BlobId, BlobType}, - error::RusticResult, + error::{ErrorKind, RusticError, RusticResult}, repository::{IndexedFull, Repository}, - ErrorKind, RusticError, }; /// Dumps the contents of a file. diff --git a/crates/core/src/crypto/aespoly1305.rs b/crates/core/src/crypto/aespoly1305.rs index 35a2e78f..cebdb2f3 100644 --- a/crates/core/src/crypto/aespoly1305.rs +++ b/crates/core/src/crypto/aespoly1305.rs @@ -4,7 +4,10 @@ use aes256ctr_poly1305aes::{ }; use rand::{thread_rng, RngCore}; -use crate::{crypto::CryptoKey, error::RusticResult, ErrorKind, RusticError}; +use crate::{ + crypto::CryptoKey, + error::{ErrorKind, RusticError, RusticResult}, +}; pub(crate) type Nonce = aead::Nonce; pub(crate) type AeadKey = aes256ctr_poly1305aes::Key; diff --git a/crates/core/src/index.rs b/crates/core/src/index.rs index 1a1e1000..9f31a6ac 100644 --- a/crates/core/src/index.rs +++ b/crates/core/src/index.rs @@ -6,14 +6,13 @@ use derive_more::Constructor; use crate::{ backend::{decrypt::DecryptReadBackend, CryptBackendErrorKind, FileType}, blob::{tree::TreeId, BlobId, BlobType, DataId}, - error::RusticResult, + error::{ErrorKind, RusticError, RusticResult}, index::binarysorted::{Index, IndexCollector, IndexType}, progress::Progress, repofile::{ indexfile::{IndexBlob, IndexFile}, packfile::PackId, }, - ErrorKind, RusticError, }; pub(crate) mod binarysorted; diff --git a/crates/core/src/repofile/configfile.rs b/crates/core/src/repofile/configfile.rs index 81febdea..0e46415d 100644 --- a/crates/core/src/repofile/configfile.rs +++ b/crates/core/src/repofile/configfile.rs @@ -4,8 +4,12 @@ use serde_derive::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::{ - backend::FileType, blob::BlobType, define_new_id_struct, error::RusticResult, impl_repofile, - repofile::RepoFile, ErrorKind, RusticError, + backend::FileType, + blob::BlobType, + define_new_id_struct, + error::{ErrorKind, RusticError, RusticResult}, + impl_repofile, + repofile::RepoFile, }; /// [`ConfigFileErrorKind`] describes the errors that can be returned for `ConfigFile`s diff --git a/crates/core/src/repofile/keyfile.rs b/crates/core/src/repofile/keyfile.rs index f1d00fa0..f8dcd8f0 100644 --- a/crates/core/src/repofile/keyfile.rs +++ b/crates/core/src/repofile/keyfile.rs @@ -7,8 +7,8 @@ use serde_with::{base64::Base64, serde_as, skip_serializing_none}; use crate::{ backend::{FileType, ReadBackend}, crypto::{aespoly1305::Key, CryptoKey}, - error::RusticResult, - impl_repoid, ErrorKind, RusticError, + error::{ErrorKind, RusticError, RusticResult}, + impl_repoid, }; /// [`KeyFileErrorKind`] describes the errors that can be returned for `KeyFile`s diff --git a/crates/core/src/repofile/packfile.rs b/crates/core/src/repofile/packfile.rs index 40f173f1..cc594546 100644 --- a/crates/core/src/repofile/packfile.rs +++ b/crates/core/src/repofile/packfile.rs @@ -6,11 +6,10 @@ use log::trace; use crate::{ backend::{decrypt::DecryptReadBackend, FileType}, blob::BlobType, - error::RusticResult, + error::{ErrorKind, RusticError, RusticResult}, id::Id, impl_repoid, repofile::indexfile::{IndexBlob, IndexPack}, - ErrorKind, RusticError, }; /// [`PackFileErrorKind`] describes the errors that can be returned for `PackFile`s diff --git a/crates/core/src/repofile/snapshotfile.rs b/crates/core/src/repofile/snapshotfile.rs index d50c7eb3..ccb598ec 100644 --- a/crates/core/src/repofile/snapshotfile.rs +++ b/crates/core/src/repofile/snapshotfile.rs @@ -21,11 +21,11 @@ use serde_with::{serde_as, skip_serializing_none, DisplayFromStr}; use crate::{ backend::{decrypt::DecryptReadBackend, FileType, FindInBackend}, blob::tree::TreeId, - error::RusticResult, + error::{ErrorKind, RusticError, RusticResult}, impl_repofile, progress::Progress, repofile::RepoFile, - ErrorKind, Id, RusticError, + Id, }; #[cfg(feature = "clap")] diff --git a/crates/core/src/vfs.rs b/crates/core/src/vfs.rs index a0bec285..295c54e0 100644 --- a/crates/core/src/vfs.rs +++ b/crates/core/src/vfs.rs @@ -18,12 +18,11 @@ pub use crate::vfs::webdavfs::WebDavFS; use crate::{ blob::{tree::TreeId, BlobId, DataId}, - error::RusticResult, + error::{ErrorKind, RusticError, RusticResult}, index::ReadIndex, repofile::{BlobType, Metadata, Node, NodeType, SnapshotFile}, repository::{IndexedFull, IndexedTree, Repository}, vfs::format::FormattedSnapshot, - ErrorKind, RusticError, }; /// [`VfsErrorKind`] describes the errors that can be returned from the Virtual File System From e5c9aa1a6a7e7aea945779a881684690791e6821 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 01:51:26 +0200 Subject: [PATCH 056/129] tests: update snapshots Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/tests/errors.rs | 2 +- crates/core/tests/snapshots/errors__error_debug.snap | 2 +- crates/core/tests/snapshots/errors__error_display.snap | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/core/tests/errors.rs b/crates/core/tests/errors.rs index 3da3dc09..eddea288 100644 --- a/crates/core/tests/errors.rs +++ b/crates/core/tests/errors.rs @@ -10,7 +10,7 @@ fn error() -> Box { ) .attach_status(Status::Permanent) .attach_severity(Severity::Error) - .attach_error_code("E001") + .attach_error_code("C001") .attach_context("path", "/path/to/file") .attach_context("called", "used s3 backend") .attach_source(std::io::Error::new( diff --git a/crates/core/tests/snapshots/errors__error_debug.snap b/crates/core/tests/snapshots/errors__error_debug.snap index af164a92..90b1718e 100644 --- a/crates/core/tests/snapshots/errors__error_debug.snap +++ b/crates/core/tests/snapshots/errors__error_debug.snap @@ -23,7 +23,7 @@ RusticError { ], docs_url: None, error_code: Some( - "E001", + "C001", ), new_issue_url: None, existing_issue_url: None, diff --git a/crates/core/tests/snapshots/errors__error_display.snap b/crates/core/tests/snapshots/errors__error_display.snap index 62a7651f..9269936b 100644 --- a/crates/core/tests/snapshots/errors__error_display.snap +++ b/crates/core/tests/snapshots/errors__error_display.snap @@ -17,7 +17,7 @@ Severity: Error Status: Permanent -For more information, see: https://rustic.cli.rs/docs/errors/E001 +For more information, see: https://rustic.cli.rs/docs/errors/C001 If you think this is an undiscovered bug, please open an issue at: https://github.com/rustic-rs/rustic_core/issues/new From 4603d44926818288c1761170c7960874189c7ade Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 02:20:47 +0200 Subject: [PATCH 057/129] make unix build Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/backend/ignore.rs | 12 ++++++----- crates/core/src/backend/node.rs | 36 +++++++++++++++---------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/crates/core/src/backend/ignore.rs b/crates/core/src/backend/ignore.rs index 87e3415a..1c0431e4 100644 --- a/crates/core/src/backend/ignore.rs +++ b/crates/core/src/backend/ignore.rs @@ -1,4 +1,6 @@ #[cfg(not(windows))] +use std::num::TryFromIntError; +#[cfg(not(windows))] use std::os::unix::fs::{FileTypeExt, MetadataExt}; use std::{ @@ -60,7 +62,7 @@ pub enum IgnoreErrorKind { CtimeConversionToTimestampFailed { ctime: i64, ctime_nsec: i64, - source: ignore::Error, + source: TryFromIntError, }, /// Error acquiring metadata for `{name}`: `{source:?}` AcquiringMetadataFailed { name: String, source: ignore::Error }, @@ -649,13 +651,13 @@ fn map_entry( let ctime = Utc .timestamp_opt( m.ctime(), - m.ctime_nsec() - .try_into() - .map_err(|err| IgnoreErrorKind::CtimeConversionFailed { + m.ctime_nsec().try_into().map_err(|err| { + IgnoreErrorKind::CtimeConversionToTimestampFailed { ctime: m.ctime(), ctime_nsec: m.ctime_nsec(), source: err, - })?, + } + })?, ) .single() .map(|dt| dt.with_timezone(&Local)); diff --git a/crates/core/src/backend/node.rs b/crates/core/src/backend/node.rs index 5127319b..55695a0e 100644 --- a/crates/core/src/backend/node.rs +++ b/crates/core/src/backend/node.rs @@ -9,12 +9,12 @@ use std::{ #[cfg(not(windows))] use std::fmt::Write; #[cfg(not(windows))] +use std::num::ParseIntError; +#[cfg(not(windows))] use std::os::unix::ffi::OsStrExt; use chrono::{DateTime, Local}; use derive_more::Constructor; -#[cfg(not(windows))] -use displaydoc::Display; use serde_aux::prelude::*; use serde_derive::{Deserialize, Serialize}; use serde_with::{ @@ -22,23 +22,21 @@ use serde_with::{ formats::Padded, serde_as, skip_serializing_none, DefaultOnNull, }; -#[cfg(not(windows))] -use thiserror::Error; use crate::blob::{tree::TreeId, DataId}; #[cfg(not(windows))] /// [`NodeErrorKind`] describes the errors that can be returned by an action utilizing a node in Backends -#[derive(thiserror::Error, Debug, Display)] +#[derive(thiserror::Error, Debug, displaydoc::Display)] #[non_exhaustive] -pub enum NodeErrorKind { +pub enum NodeErrorKind<'a> { /// Unexpected EOF while parsing filename: `{file_name}` #[cfg(not(windows))] UnexpectedEOF { /// The filename file_name: String, /// The remaining chars - chars: std::str::Chars, + chars: std::str::Chars<'a>, }, /// Invalid unicode #[cfg(not(windows))] @@ -48,7 +46,7 @@ pub enum NodeErrorKind { /// The unicode codepoint unicode: u32, /// The remaining chars - chars: std::str::Chars, + chars: std::str::Chars<'a>, }, /// Unrecognized Escape while parsing filename: `{file_name}` #[cfg(not(windows))] @@ -56,7 +54,7 @@ pub enum NodeErrorKind { /// The filename file_name: String, /// The remaining chars - chars: std::str::Chars, + chars: std::str::Chars<'a>, }, /// Parsing hex chars {chars:?} failed for `{hex}` in filename: `{file_name}` : `{source}` #[cfg(not(windows))] @@ -66,7 +64,7 @@ pub enum NodeErrorKind { /// The hex string hex: String, /// The remaining chars - chars: std::str::Chars, + chars: std::str::Chars<'a>, /// The error that occurred source: ParseIntError, }, @@ -78,14 +76,14 @@ pub enum NodeErrorKind { /// The target type target: String, /// The remaining chars - chars: std::str::Chars, + chars: std::str::Chars<'a>, /// The error that occurred source: ParseIntError, }, } #[cfg(not(windows))] -pub(crate) type NodeResult = Result; +pub(crate) type NodeResult<'a, T> = Result>; #[derive( Default, Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Constructor, PartialOrd, Ord, @@ -464,7 +462,7 @@ fn escape_filename(name: &OsStr) -> String { /// /// * `s` - The escaped filename // inspired by the enquote crate -fn unescape_filename(s: &str) -> NodeResult { +fn unescape_filename(s: &str) -> NodeResult<'_, OsString> { let mut chars = s.chars(); let mut u = Vec::new(); loop { @@ -498,7 +496,7 @@ fn unescape_filename(s: &str) -> NodeResult { NodeErrorKind::ParsingHexFailed { file_name: s.to_string(), hex: hex.to_string(), - chars, + chars: chars.clone(), source: err, } })?); @@ -509,7 +507,7 @@ fn unescape_filename(s: &str) -> NodeResult { |err| NodeErrorKind::ParsingUnicodeFailed { file_name: s.to_string(), target: "u32".to_string(), - chars, + chars: chars.clone(), source: err, }, )?; @@ -517,7 +515,7 @@ fn unescape_filename(s: &str) -> NodeResult { NodeErrorKind::InvalidUnicode { file_name: s.to_string(), unicode: n, - chars, + chars: chars.clone(), }, )?; let mut bytes = vec![0u8; c.len_utf8()]; @@ -529,7 +527,7 @@ fn unescape_filename(s: &str) -> NodeResult { |err| NodeErrorKind::ParsingUnicodeFailed { file_name: s.to_string(), target: "u32".to_string(), - chars, + chars: chars.clone(), source: err, }, )?; @@ -537,7 +535,7 @@ fn unescape_filename(s: &str) -> NodeResult { NodeErrorKind::InvalidUnicode { file_name: s.to_string(), unicode: n, - chars, + chars: chars.clone(), }, )?; let mut bytes = vec![0u8; c.len_utf8()]; @@ -547,7 +545,7 @@ fn unescape_filename(s: &str) -> NodeResult { _ => { return Err(NodeErrorKind::UnrecognizedEscape { file_name: s.to_string(), - chars, + chars: chars.clone(), }) } }, From 36e9908082e565c472c4a7e3e9b0e270fc929b25 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 02:26:43 +0200 Subject: [PATCH 058/129] fix clippy Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/rclone.rs | 1 + crates/core/src/backend/ignore.rs | 3 ++- crates/core/src/backend/local_destination.rs | 2 ++ crates/core/src/backend/node.rs | 12 ++++++------ 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/crates/backend/src/rclone.rs b/crates/backend/src/rclone.rs index 4bd3f002..fd2010fd 100644 --- a/crates/backend/src/rclone.rs +++ b/crates/backend/src/rclone.rs @@ -153,6 +153,7 @@ impl RcloneBackend { /// /// If the rclone command is not found. // TODO: This should be an error, not a panic. + #[allow(clippy::too_many_lines)] pub fn new(url: impl AsRef, options: HashMap) -> RusticResult { let rclone_command = options.get("rclone-command"); let use_password = options diff --git a/crates/core/src/backend/ignore.rs b/crates/core/src/backend/ignore.rs index 1c0431e4..58096a49 100644 --- a/crates/core/src/backend/ignore.rs +++ b/crates/core/src/backend/ignore.rs @@ -58,7 +58,7 @@ pub enum IgnoreErrorKind { source: std::io::Error, }, #[cfg(not(windows))] - /// Error converting ctime `{ctime}` and ctime_nsec `{ctime_nsec}` to Utc Timestamp: `{source:?}` + /// Error converting ctime `{ctime}` and `ctime_nsec` `{ctime_nsec}` to Utc Timestamp: `{source:?}` CtimeConversionToTimestampFailed { ctime: i64, ctime_nsec: i64, @@ -183,6 +183,7 @@ impl LocalSource { /// /// [`IgnoreErrorKind::GenericError`]: crate::error::IgnoreErrorKind::GenericError /// [`IgnoreErrorKind::FromIoError`]: crate::error::IgnoreErrorKind::FromIoError + #[allow(clippy::too_many_lines)] pub fn new( save_opts: LocalSourceSaveOptions, filter_opts: &LocalSourceFilterOptions, diff --git a/crates/core/src/backend/local_destination.rs b/crates/core/src/backend/local_destination.rs index 80363770..829b32b7 100644 --- a/crates/core/src/backend/local_destination.rs +++ b/crates/core/src/backend/local_destination.rs @@ -217,6 +217,7 @@ impl LocalDestination { /// This will remove the directory recursively. /// /// [`LocalDestinationErrorKind::DirectoryRemovalFailed`]: crate::error::LocalDestinationErrorKind::DirectoryRemovalFailed + #[allow(clippy::unused_self)] pub(crate) fn remove_dir(&self, dirname: impl AsRef) -> LocalDestinationResult<()> { fs::remove_dir_all(dirname).map_err(LocalDestinationErrorKind::DirectoryRemovalFailed) } @@ -239,6 +240,7 @@ impl LocalDestination { /// * If the file is a directory or device, this will fail. /// /// [`LocalDestinationErrorKind::FileRemovalFailed`]: crate::error::LocalDestinationErrorKind::FileRemovalFailed + #[allow(clippy::unused_self)] pub(crate) fn remove_file(&self, filename: impl AsRef) -> LocalDestinationResult<()> { fs::remove_file(filename).map_err(LocalDestinationErrorKind::FileRemovalFailed) } diff --git a/crates/core/src/backend/node.rs b/crates/core/src/backend/node.rs index 55695a0e..d6d40cd9 100644 --- a/crates/core/src/backend/node.rs +++ b/crates/core/src/backend/node.rs @@ -511,13 +511,13 @@ fn unescape_filename(s: &str) -> NodeResult<'_, OsString> { source: err, }, )?; - let c = std::char::from_u32(n).ok_or( + let c = std::char::from_u32(n).ok_or_else(|| { NodeErrorKind::InvalidUnicode { file_name: s.to_string(), unicode: n, chars: chars.clone(), - }, - )?; + } + })?; let mut bytes = vec![0u8; c.len_utf8()]; _ = c.encode_utf8(&mut bytes); u.extend_from_slice(&bytes); @@ -531,13 +531,13 @@ fn unescape_filename(s: &str) -> NodeResult<'_, OsString> { source: err, }, )?; - let c = std::char::from_u32(n).ok_or( + let c = std::char::from_u32(n).ok_or_else(|| { NodeErrorKind::InvalidUnicode { file_name: s.to_string(), unicode: n, chars: chars.clone(), - }, - )?; + } + })?; let mut bytes = vec![0u8; c.len_utf8()]; _ = c.encode_utf8(&mut bytes); u.extend_from_slice(&bytes); From 234cea6f6f3d83b0f2512199f65178b5e058368d Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 02:32:30 +0200 Subject: [PATCH 059/129] function doc Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/rclone.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/crates/backend/src/rclone.rs b/crates/backend/src/rclone.rs index fd2010fd..ab1346ca 100644 --- a/crates/backend/src/rclone.rs +++ b/crates/backend/src/rclone.rs @@ -135,15 +135,10 @@ impl RcloneBackend { /// /// # Errors /// - /// * [`RcloneErrorKind::FromIoError`] - If the rclone version could not be determined. - /// * [`RcloneErrorKind::NoStdOutForRclone`] - If the rclone version could not be determined. - /// * [`RcloneErrorKind::RCloneExitWithBadStatus`] - If rclone exited with a bad status. - /// * [`RcloneErrorKind::UrlNotStartingWithHttp`] - If the URL does not start with `http`. - /// - /// [`RcloneErrorKind::FromIoError`]: RcloneErrorKind::FromIoError - /// [`RcloneErrorKind::NoStdOutForRclone`]: RcloneErrorKind::NoStdOutForRclone - /// [`RcloneErrorKind::RCloneExitWithBadStatus`]: RcloneErrorKind::RCloneExitWithBadStatus - /// [`RcloneErrorKind::UrlNotStartingWithHttp`]: RcloneErrorKind::UrlNotStartingWithHttp + /// * If the rclone version could not be determined. + /// * If the rclone version could not be determined. + /// * If rclone exited with a bad status. + /// * If the URL does not start with `http`. /// /// # Returns /// From cd0989c32973d826ff1285ba14e1f2909724196e Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 02:32:39 +0200 Subject: [PATCH 060/129] remove unused errors from ignore Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/backend/ignore.rs | 35 +++++++++++-------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/crates/core/src/backend/ignore.rs b/crates/core/src/backend/ignore.rs index 58096a49..5a67cbd1 100644 --- a/crates/core/src/backend/ignore.rs +++ b/crates/core/src/backend/ignore.rs @@ -35,18 +35,7 @@ use crate::{ /// [`IgnoreErrorKind`] describes the errors that can be returned by a Ignore action in Backends #[derive(thiserror::Error, Debug, displaydoc::Display)] pub enum IgnoreErrorKind { - /// generic Ignore error: `{0:?}` - GenericError(ignore::Error), - /// Error reading glob file `{file:?}`: `{source:?}` - ErrorGlob { - file: PathBuf, - source: std::io::Error, - }, - /// Unable to open file `{file:?}`: `{source:?}` - UnableToOpenFile { - file: PathBuf, - source: std::io::Error, - }, + #[cfg(all(not(windows), not(target_os = "openbsd")))] /// Error getting xattrs for `{path:?}`: `{source:?}` ErrorXattr { path: PathBuf, @@ -64,6 +53,7 @@ pub enum IgnoreErrorKind { ctime_nsec: i64, source: TryFromIntError, }, + #[cfg(not(windows))] /// Error acquiring metadata for `{name}`: `{source:?}` AcquiringMetadataFailed { name: String, source: ignore::Error }, } @@ -178,11 +168,8 @@ impl LocalSource { /// /// # Errors /// - /// * [`IgnoreErrorKind::GenericError`] - If the a glob pattern could not be added to the override builder. - /// * [`IgnoreErrorKind::FromIoError`] - If a glob file could not be read. - /// - /// [`IgnoreErrorKind::GenericError`]: crate::error::IgnoreErrorKind::GenericError - /// [`IgnoreErrorKind::FromIoError`]: crate::error::IgnoreErrorKind::FromIoError + /// * If the a glob pattern could not be added to the override builder. + /// * If a glob file could not be read. #[allow(clippy::too_many_lines)] pub fn new( save_opts: LocalSourceSaveOptions, @@ -351,12 +338,14 @@ impl ReadSourceOpen for OpenFile { /// [`IgnoreErrorKind::UnableToOpenFile`]: crate::error::IgnoreErrorKind::UnableToOpenFile fn open(self) -> RusticResult { let path = self.0; - File::open(&path) - .map_err(|err| IgnoreErrorKind::UnableToOpenFile { - file: path, - source: err, - }) - .map_err(|_err| todo!("Error transition")) + File::open(&path).map_err(|err| { + RusticError::with_source( + ErrorKind::Io, + "Failed to open file. Please make sure the file exists and is accessible.", + err, + ) + .attach_context("file", path.display().to_string()) + }) } } From 1d7deafbd95e103a328c55779dec8f1aa13a6766 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 02:55:58 +0100 Subject: [PATCH 061/129] fix all `todo!("Error transition")` Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/backend.rs | 17 +- crates/core/src/backend/decrypt.rs | 71 ++++----- crates/core/src/backend/ignore.rs | 16 +- crates/core/src/blob/packer.rs | 168 ++++++++++++++------ crates/core/src/blob/tree.rs | 32 ++-- crates/core/src/commands/config.rs | 140 +++++++--------- crates/core/src/commands/prune.rs | 20 ++- crates/core/src/error.rs | 2 + crates/core/src/repofile/keyfile.rs | 16 +- crates/core/src/repository.rs | 7 +- crates/core/src/repository/command_input.rs | 13 +- crates/core/src/repository/warm_up.rs | 29 +++- crates/core/src/vfs.rs | 9 +- 13 files changed, 328 insertions(+), 212 deletions(-) diff --git a/crates/core/src/backend.rs b/crates/core/src/backend.rs index 7e10cea0..3ede1b8d 100644 --- a/crates/core/src/backend.rs +++ b/crates/core/src/backend.rs @@ -23,7 +23,7 @@ use serde_derive::{Deserialize, Serialize}; use crate::{ backend::node::{Metadata, Node, NodeType}, - error::RusticResult, + error::{ErrorKind, RusticError, RusticResult}, id::Id, }; @@ -289,14 +289,13 @@ pub trait FindInBackend: ReadBackend { .enumerate() .map(|(i, id)| match id { MapResult::Some(id) => Ok(id), - MapResult::None => Err(BackendErrorKind::NoSuitableIdFound( - (vec[i]).as_ref().to_string(), - )) - .map_err(|_err| todo!("Error transition")), - MapResult::NonUnique => { - Err(BackendErrorKind::IdNotUnique((vec[i]).as_ref().to_string())) - .map_err(|_err| todo!("Error transition")) - } + MapResult::None => Err(RusticError::new( + ErrorKind::Backend, + "No suitable id found.", + ) + .attach_context("item", vec[i].as_ref().to_string())), + MapResult::NonUnique => Err(RusticError::new(ErrorKind::Backend, "Id not unique.") + .attach_context("item", vec[i].as_ref().to_string())), }) .collect() } diff --git a/crates/core/src/backend/decrypt.rs b/crates/core/src/backend/decrypt.rs index 4cb6842f..4e4f250f 100644 --- a/crates/core/src/backend/decrypt.rs +++ b/crates/core/src/backend/decrypt.rs @@ -8,7 +8,7 @@ use zstd::stream::{copy_encode, decode_all, encode_all}; pub use zstd::compression_level_range; use crate::{ - backend::{CryptBackendErrorKind, CryptBackendResult, FileType, ReadBackend, WriteBackend}, + backend::{FileType, ReadBackend, WriteBackend}, crypto::{hasher::hash, CryptoKey}, error::{ErrorKind, RusticError, RusticResult}, id::Id, @@ -422,20 +422,22 @@ impl DecryptBackend { } /// encrypt and potentially compress a repository file - fn encrypt_file(&self, data: &[u8]) -> CryptBackendResult> { + fn encrypt_file(&self, data: &[u8]) -> RusticResult> { let data_encrypted = match self.zstd { Some(level) => { let mut out = vec![2_u8]; - copy_encode(data, &mut out, level) - .map_err(CryptBackendErrorKind::CopyEncodingDataFailed)?; - self.key() - .encrypt_data(&out) - .map_err(|_err| todo!("Error transition"))? + copy_encode(data, &mut out, level).map_err(|err| { + RusticError::with_source( + ErrorKind::Compression, + "Compressing and appending data failed. The data may be corrupted.", + err, + ) + .attach_context("compression level", level.to_string()) + })?; + + self.key().encrypt_data(&out)? } - None => self - .key() - .encrypt_data(data) - .map_err(|_err| todo!("Error transition"))?, + None => self.key().encrypt_data(data)?, }; Ok(data_encrypted) } @@ -456,25 +458,31 @@ impl DecryptBackend { } /// encrypt and potentially compress some data - fn encrypt_data(&self, data: &[u8]) -> CryptBackendResult<(Vec, u32, Option)> { + fn encrypt_data(&self, data: &[u8]) -> RusticResult<(Vec, u32, Option)> { let data_len: u32 = data .len() .try_into() - .map_err(CryptBackendErrorKind::IntConversionFailed)?; + .map_err(|err| + RusticError::with_source( + ErrorKind::Internal, + "Failed to convert data length to u32. This is likely a bug. Please report this issue.", + err, + ).attach_context("length", data.len().to_string()) + )?; + let (data_encrypted, uncompressed_length) = match self.zstd { - None => ( - self.key - .encrypt_data(data) - .map_err(|_err| todo!("Error transition"))?, - None, - ), + None => (self.key.encrypt_data(data)?, None), // compress if requested Some(level) => ( self.key - .encrypt_data( - &encode_all(data, level).map_err(|_err| todo!("Error transition"))?, - ) - .map_err(|_err| todo!("Error transition"))?, + .encrypt_data(&encode_all(data, level).map_err(|err| { + RusticError::with_source( + ErrorKind::Compression, + "Failed to encode zstd compressed data. The data may be corrupted.", + err, + ) + .attach_context("compression level", level.to_string()) + })?)?, NonZeroU32::new(data_len), ), }; @@ -531,13 +539,7 @@ impl DecryptWriteBackend for DecryptBackend { /// /// [`CryptBackendErrorKind::CopyEncodingDataFailed`]: crate::error::CryptBackendErrorKind::CopyEncodingDataFailed fn hash_write_full(&self, tpe: FileType, data: &[u8]) -> RusticResult { - let data_encrypted = self.encrypt_file(data).map_err(|err| { - RusticError::with_source( - ErrorKind::Cryptography, - "Failed to encrypt data. The data may be corrupted.", - err, - ) - })?; + let data_encrypted = self.encrypt_file(data)?; self.very_file(&data_encrypted, data)?; @@ -548,14 +550,7 @@ impl DecryptWriteBackend for DecryptBackend { } fn process_data(&self, data: &[u8]) -> RusticResult<(Vec, u32, Option)> { - let (data_encrypted, data_len, uncompressed_length) = - self.encrypt_data(data).map_err(|err| { - RusticError::with_source( - ErrorKind::Cryptography, - "Failed to encrypt data. The data may be corrupted.", - err, - ) - })?; + let (data_encrypted, data_len, uncompressed_length) = self.encrypt_data(data)?; self.very_data(&data_encrypted, uncompressed_length, data)?; diff --git a/crates/core/src/backend/ignore.rs b/crates/core/src/backend/ignore.rs index 5a67cbd1..b1d569e8 100644 --- a/crates/core/src/backend/ignore.rs +++ b/crates/core/src/backend/ignore.rs @@ -35,6 +35,8 @@ use crate::{ /// [`IgnoreErrorKind`] describes the errors that can be returned by a Ignore action in Backends #[derive(thiserror::Error, Debug, displaydoc::Display)] pub enum IgnoreErrorKind { + /// Failed to get metadata for entry: `{source:?}` + FailedToGetMetadata { source: ignore::Error }, #[cfg(all(not(windows), not(target_os = "openbsd")))] /// Error getting xattrs for `{path:?}`: `{source:?}` ErrorXattr { @@ -414,12 +416,18 @@ impl Iterator for LocalSourceWalker { ErrorKind::Internal, "Failed to get next entry from walk iterator. This is a bug. Please report it.", err, - ) + ) )?, self.save_opts.with_atime, self.save_opts.ignore_devid, ) - .map_err(|_err| todo!("Error transition")) + .map_err(|err| + RusticError::with_source( + ErrorKind::Internal, + "Failed to map Directory entry to ReadSourceEntry. This is a bug. Please report it.", + err, + ) + ) }) } } @@ -447,7 +455,9 @@ fn map_entry( _ignore_devid: bool, ) -> IgnoreResult> { let name = entry.file_name(); - let m = entry.metadata().map_err(|_err| todo!("Error transition"))?; + let m = entry + .metadata() + .map_err(|err| IgnoreErrorKind::FailedToGetMetadata { source: err })?; // TODO: Set them to suitable values let uid = None; diff --git a/crates/core/src/blob/packer.rs b/crates/core/src/blob/packer.rs index bb0eb4ea..398595fd 100644 --- a/crates/core/src/blob/packer.rs +++ b/crates/core/src/blob/packer.rs @@ -33,13 +33,26 @@ use crate::{ #[non_exhaustive] pub enum PackerErrorKind { /// getting total size failed - GettingTotalSizeFailed, - /// Conversion from `{from}` to `{to}` failed: {source} - ConversionFailed { + GettingTotalSize, + /// Conversion from `{from}` to `{to}` failed: `{source}` + Conversion { to: &'static str, from: &'static str, source: std::num::TryFromIntError, }, + /// Sending crossbeam message failed: `size_limit`: `{size_limit:?}`, `id`: `{id:?}`, `data`: `{data:?}` : `{source}` + SendingCrossbeamMessage { + size_limit: Option, + id: BlobId, + data: Bytes, + source: crossbeam_channel::SendError<(Bytes, BlobId, Option)>, + }, + /// Sending crossbeam data message failed: `data`: `{data:?}`, `index_pack`: `{index_pack:?}` : `{source}` + SendingCrossbeamDataMessage { + data: Bytes, + index_pack: IndexPack, + source: crossbeam_channel::SendError<(Bytes, IndexPack)>, + }, } pub(crate) type PackerResult = Result; @@ -270,7 +283,6 @@ impl Packer { .write() .unwrap() .add_raw(&data, &id, data_len, ul, size_limit) - .map_err(|_err| todo!("Error transition")) }) .and_then(|()| raw_packer.write().unwrap().finalize()); _ = finish_tx.send(status); @@ -295,8 +307,14 @@ impl Packer { /// [`PackerErrorKind::SendingCrossbeamMessageFailed`]: crate::error::PackerErrorKind::SendingCrossbeamMessageFailed pub fn add(&self, data: Bytes, id: BlobId) -> RusticResult<()> { // compute size limit based on total size and size bounds - self.add_with_sizelimit(data, id, None) - .map_err(|_err| todo!("Error transition")) + self.add_with_sizelimit(data, id, None).map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to add blob to packfile. This is a bug. Please report this issue and upload the log file.", + err + ) + .attach_context("blob id", id.to_string()) + }) } /// Adds the blob to the packfile, allows specifying a size limit for the pack file @@ -319,8 +337,13 @@ impl Packer { size_limit: Option, ) -> PackerResult<()> { self.sender - .send((data, id, size_limit)) - .map_err(|_err| todo!("Error transition"))?; + .send((data.clone(), id, size_limit)) + .map_err(|err| PackerErrorKind::SendingCrossbeamMessage { + size_limit, + id, + data, + source: err, + })?; Ok(()) } @@ -345,7 +368,7 @@ impl Packer { data_len: u64, uncompressed_length: Option, size_limit: Option, - ) -> PackerResult<()> { + ) -> RusticResult<()> { // only add if this blob is not present if self.indexer.read().unwrap().has(id) { Ok(()) @@ -523,7 +546,7 @@ impl RawPacker { let len = data .len() .try_into() - .map_err(|err| PackerErrorKind::ConversionFailed { + .map_err(|err| PackerErrorKind::Conversion { to: "u32", from: "usize", source: err, @@ -555,27 +578,43 @@ impl RawPacker { data_len: u64, uncompressed_length: Option, size_limit: Option, - ) -> PackerResult<()> { + ) -> RusticResult<()> { if self.has(id) { return Ok(()); } self.stats.blobs += 1; + self.stats.data += data_len; - let data_len_packed: u64 = - data.len() - .try_into() - .map_err(|err| PackerErrorKind::ConversionFailed { - to: "u64", - from: "usize", - source: err, - })?; + + let data_len_packed: u64 = data.len().try_into().map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to convert data length to u64.", + err, + ) + .attach_context("data length", data.len().to_string()) + })?; + self.stats.data_packed += data_len_packed; let size_limit = size_limit.unwrap_or_else(|| self.pack_sizer.pack_size()); + let offset = self.size; - let len = self.write_data(data)?; + + let len = self.write_data(data).map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to write data to packfile.", + err, + ) + .attach_context("blob id", id.to_string()) + .attach_context("size limit", size_limit.to_string()) + .attach_context("data length packed", data_len_packed.to_string()) + })?; + self.index .add(*id, self.blob_type, offset, len, uncompressed_length); + self.count += 1; // check if PackFile needs to be saved @@ -583,6 +622,7 @@ impl RawPacker { warn!("couldn't get elapsed time from system time: {err:?}"); Duration::ZERO }); + if self.count >= constants::MAX_COUNT || self.size >= size_limit || elapsed >= constants::MAX_AGE @@ -605,35 +645,62 @@ impl RawPacker { /// /// [`PackerErrorKind::IntConversionFailed`]: crate::error::PackerErrorKind::IntConversionFailed /// [`PackFileErrorKind::WritingBinaryRepresentationFailed`]: crate::error::PackFileErrorKind::WritingBinaryRepresentationFailed - fn write_header(&mut self) -> PackerResult<()> { + fn write_header(&mut self) -> RusticResult<()> { // compute the pack header let data = PackHeaderRef::from_index_pack(&self.index) .to_binary() - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| -> Box { + RusticError::with_source( + ErrorKind::Internal, + "Failed to convert pack header to binary representation.", + err, + ) + .attach_context("index pack id", self.index.id.to_string()) + })?; // encrypt and write to pack file - let data = self - .be - .key() - .encrypt_data(&data) - .map_err(|_err| todo!("Error transition"))?; + let data = self.be.key().encrypt_data(&data)?; - let headerlen = data - .len() - .try_into() - .map_err(|err| PackerErrorKind::ConversionFailed { - to: "u32", - from: "usize", - source: err, + let headerlen: u32 = data.len().try_into().map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to convert header length to u32.", + err, + ) + .attach_context("header length", data.len().to_string()) + })?; + + // write header to pack file + _ = self.write_data(&data).map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to write header to packfile.", + err, + ) + .attach_context("header length", headerlen.to_string()) + })?; + + // convert header length to binary representation + let binary_repr = PackHeaderLength::from_u32(headerlen) + .to_binary() + .map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to convert header length to binary representation.", + err, + ) + .attach_context("header length", headerlen.to_string()) })?; - _ = self.write_data(&data)?; // finally write length of header unencrypted to pack file - _ = self.write_data( - &PackHeaderLength::from_u32(headerlen) - .to_binary() - .map_err(|_err| todo!("Error transition"))?, - )?; + _ = self.write_data(&binary_repr).map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to write header length to packfile.", + err, + ) + .attach_context("header length", headerlen.to_string()) + })?; Ok(()) } @@ -651,7 +718,7 @@ impl RawPacker { /// /// [`PackerErrorKind::IntConversionFailed`]: crate::error::PackerErrorKind::IntConversionFailed /// [`PackFileErrorKind::WritingBinaryRepresentationFailed`]: crate::error::PackFileErrorKind::WritingBinaryRepresentationFailed - fn save(&mut self) -> PackerResult<()> { + fn save(&mut self) -> RusticResult<()> { if self.size == 0 { return Ok(()); } @@ -664,7 +731,14 @@ impl RawPacker { self.file_writer .as_ref() .unwrap() - .send((file.into(), index))?; + .send((file.into(), index)) + .map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to send packfile to file writer.", + err, + ) + })?; Ok(()) } @@ -767,9 +841,13 @@ impl Actor { /// /// If sending the message to the actor fails. fn send(&self, load: (Bytes, IndexPack)) -> PackerResult<()> { - self.sender - .send(load) - .map_err(|_err| todo!("Error transition"))?; + self.sender.send(load.clone()).map_err(|err| { + PackerErrorKind::SendingCrossbeamDataMessage { + data: load.0, + index_pack: load.1, + source: err, + } + })?; Ok(()) } diff --git a/crates/core/src/blob/tree.rs b/crates/core/src/blob/tree.rs index 5449bece..dd3dc6d6 100644 --- a/crates/core/src/blob/tree.rs +++ b/crates/core/src/blob/tree.rs @@ -909,16 +909,18 @@ impl Iterator for TreeStreamerOnce

{ self.p.finish(); return None; } + let (path, tree, count) = match self.queue_out.recv() { Ok(Ok(res)) => res, Err(err) => { - return Some( - Err(TreeErrorKind::Channel { - kind: "receiving crossbeam message", - source: err.into(), - }) - .map_err(|_err| todo!("Error transition")), - ) + return Some(Err( + RusticError::with_source( + ErrorKind::Internal, + "Failed to receive tree from crossbeam channel. This is a bug. Please report it.", + err, + ) + .attach_context("finished ids", self.finished_ids.to_string()) + )); } Ok(Err(err)) => return Some(Err(err)), }; @@ -927,17 +929,29 @@ impl Iterator for TreeStreamerOnce

{ if let Some(id) = node.subtree { let mut path = path.clone(); path.push(node.name()); - match self.add_pending(path, id, count) { + match self.add_pending(path.clone(), id, count) { Ok(_) => {} - Err(err) => return Some(Err(err).map_err(|_err| todo!("Error transition"))), + Err(err) => return Some(Err(err).map_err(|err| + RusticError::with_source( + ErrorKind::Internal, + "Failed to add tree ID to pending queue. This is a bug. Please report it.", + err, + ) + .attach_context("path", path.display().to_string()) + .attach_context("tree id", id.to_string()) + .attach_context("count", count.to_string()) + )), } } } + self.counter[count] -= 1; + if self.counter[count] == 0 { self.p.inc(1); self.finished_ids += 1; } + Some(Ok((path, tree))) } } diff --git a/crates/core/src/commands/config.rs b/crates/core/src/commands/config.rs index 9badf7c3..bf200b68 100644 --- a/crates/core/src/commands/config.rs +++ b/crates/core/src/commands/config.rs @@ -1,6 +1,4 @@ //! `config` subcommand -use std::ops::RangeInclusive; - use bytesize::ByteSize; use derive_setters::Setters; @@ -12,29 +10,6 @@ use crate::{ repository::{Open, Repository}, }; -#[non_exhaustive] -#[derive(thiserror::Error, Debug, displaydoc::Display)] -pub enum ConfigCommandErrorKind { - /// Not allowed on an append-only repository: `{0}` - NotAllowedWithAppendOnly(String), - /// compression level `{0}` is not supported for repo v1 - NoCompressionV1Repo(i32), - /// version `{0}` is not supported. Allowed values: {1:?} - VersionNotSupported(u32, RangeInclusive), - /// compression level `{0}` is not supported. Allowed values: `{1:?}` - CompressionLevelNotSupported(i32, RangeInclusive), - /// cannot downgrade version from `{0}` to `{1}` - CannotDowngrade(u32, u32), - /// Size is too large: `{0}` - SizeTooLarge(ByteSize), - /// `min_packsize_tolerate_percent` must be <= 100 - MinPackSizeTolerateWrong, - /// `max_packsize_tolerate_percent` must be >= 100 or 0" - MaxPackSizeTolerateWrong, -} - -pub(crate) type ConfigCommandResult = Result; - /// Apply the [`ConfigOptions`] to a given [`ConfigFile`] /// /// # Type Parameters @@ -49,36 +24,27 @@ pub(crate) type ConfigCommandResult = Result; /// /// # Errors /// -/// * [`ConfigCommandErrorKind::VersionNotSupported`] - If the version is not supported -/// * [`ConfigCommandErrorKind::CannotDowngrade`] - If the version is lower than the current version -/// * [`ConfigCommandErrorKind::NoCompressionV1Repo`] - If compression is set for a v1 repo -/// * [`ConfigCommandErrorKind::CompressionLevelNotSupported`] - If the compression level is not supported -/// * [`ConfigCommandErrorKind::SizeTooLarge`] - If the size is too large -/// * [`ConfigCommandErrorKind::MinPackSizeTolerateWrong`] - If the min packsize tolerance percent is wrong -/// * [`ConfigCommandErrorKind::MaxPackSizeTolerateWrong`] - If the max packsize tolerance percent is wrong -/// * [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`] - If the file could not be serialized to json. +/// * If the version is not supported. +/// * If the version is lower than the current version. +/// * If compression is set for a v1 repo. +/// * If the compression level is not supported. +/// * If the size is too large. +/// * If the min pack size tolerance percent is wrong. +/// * If the max pack size tolerance percent is wrong. +/// * If the file could not be serialized to json. /// /// # Returns /// /// Whether the config was changed -/// -/// [`ConfigCommandErrorKind::VersionNotSupported`]: ConfigCommandErrorKind::VersionNotSupported -/// [`ConfigCommandErrorKind::CannotDowngrade`]: ConfigCommandErrorKind::CannotDowngrade -/// [`ConfigCommandErrorKind::NoCompressionV1Repo`]: ConfigCommandErrorKind::NoCompressionV1Repo -/// [`ConfigCommandErrorKind::CompressionLevelNotSupported`]: ConfigCommandErrorKind::CompressionLevelNotSupported -/// [`ConfigCommandErrorKind::SizeTooLarge`]: ConfigCommandErrorKind::SizeTooLarge -/// [`ConfigCommandErrorKind::MinPackSizeTolerateWrong`]: ConfigCommandErrorKind::MinPackSizeTolerateWrong -/// [`ConfigCommandErrorKind::MaxPackSizeTolerateWrong`]: ConfigCommandErrorKind::MaxPackSizeTolerateWrong -/// [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`]: crate::error::CryptBackendErrorKind::SerializingToJsonByteVectorFailed pub(crate) fn apply_config( repo: &Repository, opts: &ConfigOptions, ) -> RusticResult { if repo.config().append_only == Some(true) { - return Err(ConfigCommandErrorKind::NotAllowedWithAppendOnly( - "config change".to_string(), - )) - .map_err(|_err| todo!("Error transition")); + return Err(RusticError::new( + ErrorKind::Permission, + "Not allowed to change config on an append-only repository.", + )); } let mut new_config = repo.config().clone(); @@ -106,9 +72,7 @@ pub(crate) fn apply_config( /// /// # Errors /// -/// * [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`] - If the file could not be serialized to json. -/// -/// [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`]: crate::error::CryptBackendErrorKind::SerializingToJsonByteVectorFailed +/// * If the file could not be serialized to json. pub(crate) fn save_config( repo: &Repository, mut new_config: ConfigFile, @@ -213,49 +177,54 @@ impl ConfigOptions { /// /// # Errors /// - /// * [`ConfigCommandErrorKind::VersionNotSupported`] - If the version is not supported - /// * [`ConfigCommandErrorKind::CannotDowngrade`] - If the version is lower than the current version - /// * [`ConfigCommandErrorKind::NoCompressionV1Repo`] - If compression is set for a v1 repo - /// * [`ConfigCommandErrorKind::CompressionLevelNotSupported`] - If the compression level is not supported - /// * [`ConfigCommandErrorKind::SizeTooLarge`] - If the size is too large - /// * [`ConfigCommandErrorKind::MinPackSizeTolerateWrong`] - If the min packsize tolerate percent is wrong - /// * [`ConfigCommandErrorKind::MaxPackSizeTolerateWrong`] - If the max packsize tolerate percent is wrong - /// - /// [`ConfigCommandErrorKind::VersionNotSupported`]: ConfigCommandErrorKind::VersionNotSupported - /// [`ConfigCommandErrorKind::CannotDowngrade`]: ConfigCommandErrorKind::CannotDowngrade - /// [`ConfigCommandErrorKind::NoCompressionV1Repo`]: ConfigCommandErrorKind::NoCompressionV1Repo - /// [`ConfigCommandErrorKind::CompressionLevelNotSupported`]: ConfigCommandErrorKind::CompressionLevelNotSupported - /// [`ConfigCommandErrorKind::SizeTooLarge`]: ConfigCommandErrorKind::SizeTooLarge - /// [`ConfigCommandErrorKind::MinPackSizeTolerateWrong`]: ConfigCommandErrorKind::MinPackSizeTolerateWrong - /// [`ConfigCommandErrorKind::MaxPackSizeTolerateWrong`]: ConfigCommandErrorKind::MaxPackSizeTolerateWrong + /// * If the version is not supported + /// * If the version is lower than the current version + /// * If compression is set for a v1 repo + /// * If the compression level is not supported + /// * If the size is too large + /// * If the min packsize tolerate percent is wrong + /// * If the max packsize tolerate percent is wrong pub fn apply(&self, config: &mut ConfigFile) -> RusticResult<()> { if let Some(version) = self.set_version { + // only allow versions 1 and 2 let range = 1..=2; + if !range.contains(&version) { - return Err(ConfigCommandErrorKind::VersionNotSupported(version, range)) - .map_err(|_err| todo!("Error transition")); + return Err(RusticError::new( + ErrorKind::Unsupported, + "Config version not supported.", + ) + .attach_context("current version", version.to_string()) + .attach_context("allowed versions", format!("{range:?}"))); } else if version < config.version { - return Err(ConfigCommandErrorKind::CannotDowngrade( - config.version, - version, - )) - .map_err(|_err| todo!("Error transition")); + return Err(RusticError::new( + ErrorKind::Unsupported, + "Downgrading config version is not supported. Please use a higher version.", + ) + .attach_context("current version", config.version.to_string()) + .attach_context("new version", version.to_string())); } + config.version = version; } if let Some(compression) = self.set_compression { if config.version == 1 && compression != 0 { - return Err(ConfigCommandErrorKind::NoCompressionV1Repo(compression)) - .map_err(|_err| todo!("Error transition")); + return Err(RusticError::new( + ErrorKind::Unsupported, + "Compression not supported for v1 repos.", + ) + .attach_context("compression", compression.to_string())); } + let range = zstd::compression_level_range(); if !range.contains(&compression) { - return Err(ConfigCommandErrorKind::CompressionLevelNotSupported( - compression, - range, - )) - .map_err(|_err| todo!("Error transition")); + return Err(RusticError::new( + ErrorKind::Unsupported, + "Compression level not supported.", + ) + .attach_context("compression", compression.to_string()) + .attach_context("allowed levels", format!("{range:?}"))); } config.compression = Some(compression); } @@ -302,16 +271,23 @@ impl ConfigOptions { if let Some(percent) = self.set_min_packsize_tolerate_percent { if percent > 100 { - return Err(ConfigCommandErrorKind::MinPackSizeTolerateWrong) - .map_err(|_err| todo!("Error transition")); + return Err(RusticError::new( + ErrorKind::InvalidInput, + "`min_packsize_tolerate_percent` must be <= 100.", + ) + .attach_context("percent", percent.to_string())); } + config.min_packsize_tolerate_percent = Some(percent); } if let Some(percent) = self.set_max_packsize_tolerate_percent { if percent < 100 && percent > 0 { - return Err(ConfigCommandErrorKind::MaxPackSizeTolerateWrong) - .map_err(|_err| todo!("Error transition")); + return Err(RusticError::new( + ErrorKind::InvalidInput, + "`max_packsize_tolerate_percent` must be >= 100 or 0.", + ) + .attach_context("percent", percent.to_string())); } config.max_packsize_tolerate_percent = Some(percent); } diff --git a/crates/core/src/commands/prune.rs b/crates/core/src/commands/prune.rs index 1e6738d6..c15d2a46 100644 --- a/crates/core/src/commands/prune.rs +++ b/crates/core/src/commands/prune.rs @@ -198,16 +198,30 @@ pub enum LimitOption { } impl FromStr for LimitOption { - type Err = RusticError; + type Err = Box; fn from_str(s: &str) -> Result { Ok(match s.chars().last().unwrap_or('0') { '%' => Self::Percentage({ let mut copy = s.to_string(); _ = copy.pop(); - copy.parse().map_err(|_err| todo!("Error transition"))? + copy.parse().map_err(|err| { + RusticError::with_source( + ErrorKind::Parsing, + "Failed to parse percentage limit.", + err, + ) + .attach_context("limit", s) + })? }), 'd' if s == "unlimited" => Self::Unlimited, - _ => Self::Size(ByteSize::from_str(s).map_err(|_err| todo!("Error transition"))?), + _ => { + let byte_size = ByteSize::from_str(s).map_err(|err| { + RusticError::with_source(ErrorKind::Parsing, "Failed to parse size limit.", err) + .attach_context("limit", s) + })?; + + Self::Size(byte_size) + } }) } } diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 2ad6070b..b2679c31 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -393,6 +393,8 @@ pub enum ErrorKind { Internal, /// IO Error Io, + /// Invalid Input Error + InvalidInput, /// Key Error Key, /// Multithreading Error diff --git a/crates/core/src/repofile/keyfile.rs b/crates/core/src/repofile/keyfile.rs index f8dcd8f0..ab839234 100644 --- a/crates/core/src/repofile/keyfile.rs +++ b/crates/core/src/repofile/keyfile.rs @@ -273,7 +273,7 @@ impl KeyFile { /// /// # Errors /// - // TODO!: Add errors! + /// * If the [`KeyFile`] could not be deserialized/read from the backend /// /// # Returns /// @@ -281,14 +281,14 @@ impl KeyFile { fn from_backend(be: &B, id: &KeyId) -> RusticResult { let data = be.read_full(FileType::Key, id)?; - serde_json::from_slice(&data) - .map_err( - |err| KeyFileErrorKind::DeserializingFromSliceForKeyIdFailed { - key_id: *id, - source: err, - }, + serde_json::from_slice(&data).map_err(|err| { + RusticError::with_source( + ErrorKind::Key, + "Couldn't deserialize the data for key.", + err, ) - .map_err(|_err| todo!("Error transition")) + .attach_context("key id", id.to_string()) + }) } } diff --git a/crates/core/src/repository.rs b/crates/core/src/repository.rs index 60e20040..8b51a573 100644 --- a/crates/core/src/repository.rs +++ b/crates/core/src/repository.rs @@ -764,7 +764,8 @@ impl Repository { /// /// The result of the warm up pub fn warm_up(&self, packs: impl ExactSizeIterator) -> RusticResult<()> { - warm_up(self, packs).map_err(|_err| todo!("Error transition")) + warm_up(self, packs) + .map_err(|err| RusticError::with_source(ErrorKind::Command, "Warm-up failed.", err)) } /// Warm up the given pack files and wait the configured waiting time. @@ -781,7 +782,9 @@ impl Repository { /// [`RusticErrorKind::FromSplitError`]: crate::error::RusticErrorKind::FromSplitError /// [`RusticErrorKind::FromThreadPoolbilderError`]: crate::error::RusticErrorKind::FromThreadPoolbilderError pub fn warm_up_wait(&self, packs: impl ExactSizeIterator) -> RusticResult<()> { - warm_up_wait(self, packs).map_err(|_err| todo!("Error transition")) + warm_up_wait(self, packs).map_err(|err| { + RusticError::with_source(ErrorKind::Command, "Warm-up with waiting time failed.", err) + }) } } diff --git a/crates/core/src/repository/command_input.rs b/crates/core/src/repository/command_input.rs index 0bb8e742..fd424f2f 100644 --- a/crates/core/src/repository/command_input.rs +++ b/crates/core/src/repository/command_input.rs @@ -8,7 +8,7 @@ use log::{debug, error, trace, warn}; use serde::{Deserialize, Serialize, Serializer}; use serde_with::{serde_as, DisplayFromStr, PickFirst}; -use crate::error::RusticResult; +use crate::error::{ErrorKind, RusticError, RusticResult}; /// [`CommandInputErrorKind`] describes the errors that can be returned from the `CommandInput` #[derive(thiserror::Error, Debug, displaydoc::Display)] @@ -219,7 +219,7 @@ impl OnFailure { } } - /// Displays a result depending on the defined error handling which still yielding the same result + /// Displays a result depending on the defined error handling while still yielding the same result /// /// # Note /// @@ -237,7 +237,14 @@ impl OnFailure { Self::Ignore => {} } } - res.map_err(|_err| todo!("Error transition")) + + res.map_err(|err| { + RusticError::with_source( + ErrorKind::ExternalCommand, + "Experienced an error while calling an external command.", + err, + ) + }) } /// Handle a status of a called command depending on the defined error handling diff --git a/crates/core/src/repository/warm_up.rs b/crates/core/src/repository/warm_up.rs index 211de677..a28d969d 100644 --- a/crates/core/src/repository/warm_up.rs +++ b/crates/core/src/repository/warm_up.rs @@ -6,6 +6,7 @@ use rayon::ThreadPoolBuilder; use crate::{ backend::{FileType, ReadBackend}, + error::{ErrorKind, RusticError, RusticResult}, progress::{Progress, ProgressBars}, repofile::packfile::PackId, repository::Repository, @@ -44,7 +45,7 @@ pub(super) mod constants { pub(crate) fn warm_up_wait( repo: &Repository, packs: impl ExactSizeIterator, -) -> WarmupResult<()> { +) -> RusticResult<()> { warm_up(repo, packs)?; if let Some(wait) = repo.opts.warm_up_wait { let p = repo.pb.progress_spinner(format!("waiting {wait}...")); @@ -71,7 +72,7 @@ pub(crate) fn warm_up_wait( pub(crate) fn warm_up( repo: &Repository, packs: impl ExactSizeIterator, -) -> WarmupResult<()> { +) -> RusticResult<()> { if let Some(warm_up_cmd) = &repo.opts.warm_up_command { warm_up_command(packs, warm_up_cmd, &repo.pb)?; } else if repo.be.needs_warm_up() { @@ -97,7 +98,7 @@ fn warm_up_command( packs: impl ExactSizeIterator, command: &CommandInput, pb: &P, -) -> WarmupResult<()> { +) -> RusticResult<()> { let p = pb.progress_counter("warming up packs..."); p.set_length(packs.len() as u64); for pack in packs { @@ -106,11 +107,21 @@ fn warm_up_command( .iter() .map(|c| c.replace("%id", &pack.to_hex())) .collect(); + debug!("calling {command:?}..."); + let status = Command::new(command.command()) .args(&args) .status() - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| { + RusticError::with_source( + ErrorKind::ExternalCommand, + "Error in executing warm-up command.", + err, + ) + .attach_context("command", command.to_string()) + })?; + if !status.success() { warn!("warm-up command was not successful for pack {pack:?}. {status}"); } @@ -134,14 +145,20 @@ fn warm_up_command( fn warm_up_repo( repo: &Repository, packs: impl ExactSizeIterator, -) -> WarmupResult<()> { +) -> RusticResult<()> { let progress_bar = repo.pb.progress_counter("warming up packs..."); progress_bar.set_length(packs.len() as u64); let pool = ThreadPoolBuilder::new() .num_threads(constants::MAX_READER_THREADS_NUM) .build() - .map_err(|_err| todo!("Error transition"))?; + .map_err(|err| { + RusticError::with_source( + ErrorKind::Multithreading, + "Failed to create thread pool for warm-up", + err, + ) + })?; let progress_bar_ref = &progress_bar; let backend = &repo.be; pool.in_place_scope(|scope| { diff --git a/crates/core/src/vfs.rs b/crates/core/src/vfs.rs index 295c54e0..1f5ad9fe 100644 --- a/crates/core/src/vfs.rs +++ b/crates/core/src/vfs.rs @@ -28,8 +28,6 @@ use crate::{ /// [`VfsErrorKind`] describes the errors that can be returned from the Virtual File System #[derive(thiserror::Error, Debug, displaydoc::Display)] pub enum VfsErrorKind { - /// No directory entries for symlink found: `{0:?}` - NoDirectoryEntriesForSymlinkFound(OsString), /// Directory exists as non-virtual directory DirectoryExistsAsNonVirtual, /// Only normal paths allowed @@ -447,8 +445,11 @@ impl Vfs { }) .collect(), VfsPath::Link(str) => { - return Err(VfsErrorKind::NoDirectoryEntriesForSymlinkFound(str.clone())) - .map_err(|_err| todo!("Error transition")); + return Err(RusticError::new( + ErrorKind::Vfs, + "No directory entries for symlink found. Is the path valid unicode?", + ) + .attach_context("symlink", str.to_string_lossy().to_string())); } }; Ok(result) From a480693aec3cad24f016845a2daf121f5edc8cb9 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 03:08:01 +0100 Subject: [PATCH 062/129] Remove compression from error kind and replace with Internal variant Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/backend/decrypt.rs | 10 +++++----- crates/core/src/backend/dry_run.rs | 2 +- crates/core/src/error.rs | 2 -- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/crates/core/src/backend/decrypt.rs b/crates/core/src/backend/decrypt.rs index 4e4f250f..665b349a 100644 --- a/crates/core/src/backend/decrypt.rs +++ b/crates/core/src/backend/decrypt.rs @@ -79,14 +79,14 @@ pub trait DecryptReadBackend: ReadBackend + Clone + 'static { if let Some(length) = uncompressed_length { data = decode_all(&*data).map_err(|err| { RusticError::with_source( - ErrorKind::Compression, + ErrorKind::Internal, "Failed to decode zstd compressed data. The data may be corrupted.", err, ) })?; if data.len() != length.get() as usize { return Err(RusticError::new( - ErrorKind::Compression, + ErrorKind::Internal, "Length of uncompressed data does not match the given length. This is likely a bug. Please report this issue.", ) .attach_context("expected_length", length.get().to_string()) @@ -407,7 +407,7 @@ impl DecryptBackend { Some(b'{' | b'[') => decrypted, // not compressed Some(2) => decode_all(&decrypted[1..]).map_err(|err| { RusticError::with_source( - ErrorKind::Compression, + ErrorKind::Internal, "Failed to decode zstd compressed data. The data may be corrupted.", err, ) @@ -428,7 +428,7 @@ impl DecryptBackend { let mut out = vec![2_u8]; copy_encode(data, &mut out, level).map_err(|err| { RusticError::with_source( - ErrorKind::Compression, + ErrorKind::Internal, "Compressing and appending data failed. The data may be corrupted.", err, ) @@ -477,7 +477,7 @@ impl DecryptBackend { self.key .encrypt_data(&encode_all(data, level).map_err(|err| { RusticError::with_source( - ErrorKind::Compression, + ErrorKind::Internal, "Failed to encode zstd compressed data. The data may be corrupted.", err, ) diff --git a/crates/core/src/backend/dry_run.rs b/crates/core/src/backend/dry_run.rs index ff65c82f..5b2f60f4 100644 --- a/crates/core/src/backend/dry_run.rs +++ b/crates/core/src/backend/dry_run.rs @@ -65,7 +65,7 @@ impl DecryptReadBackend for DryRunBackend { Some(2) => decode_all(&decrypted[1..]) .map_err(|err| RusticError::with_source( - ErrorKind::Compression, + ErrorKind::Internal, "Decoding zstd compressed data failed. This can happen if the data is corrupted. Please check the backend for corruption and try again. You can also try to run `rustic check` to check for corruption.", err ) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index b2679c31..08e4fd78 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -380,8 +380,6 @@ pub enum ErrorKind { Backend, /// Command Error Command, - /// Compression Error - Compression, /// Config Error Config, /// Crypto Error From 56c45af936dac029b49818f4638cedfd0bc0d180 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 03:58:57 +0100 Subject: [PATCH 063/129] remove long error types from function docs Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/choose.rs | 6 +- crates/backend/src/local.rs | 39 +-- crates/backend/src/opendal.rs | 2 +- crates/backend/src/rclone.rs | 27 +- crates/backend/src/rest.rs | 4 +- crates/backend/src/util.rs | 2 +- crates/core/src/archiver.rs | 17 +- crates/core/src/archiver/file_archiver.rs | 13 +- crates/core/src/archiver/parent.rs | 8 +- crates/core/src/archiver/tree_archiver.rs | 21 +- crates/core/src/backend.rs | 51 ++- crates/core/src/backend/cache.rs | 46 +-- crates/core/src/backend/decrypt.rs | 47 +-- crates/core/src/backend/dry_run.rs | 7 +- crates/core/src/backend/ignore.rs | 22 +- crates/core/src/backend/local_destination.rs | 104 +++---- crates/core/src/backend/node.rs | 14 +- crates/core/src/blob/packer.rs | 57 ++-- crates/core/src/blob/tree.rs | 53 +--- crates/core/src/commands/backup.rs | 16 +- crates/core/src/commands/cat.rs | 18 +- crates/core/src/commands/check.rs | 18 +- crates/core/src/commands/dump.rs | 4 +- crates/core/src/commands/forget.rs | 4 +- crates/core/src/commands/init.rs | 4 +- crates/core/src/commands/key.rs | 8 +- crates/core/src/commands/merge.rs | 4 +- crates/core/src/commands/prune.rs | 44 +-- crates/core/src/commands/repair/snapshots.rs | 2 +- crates/core/src/commands/repoinfo.rs | 2 +- crates/core/src/commands/restore.rs | 22 +- crates/core/src/crypto/aespoly1305.rs | 4 +- crates/core/src/id.rs | 14 +- crates/core/src/index.rs | 8 +- crates/core/src/index/indexer.rs | 20 +- crates/core/src/repofile/configfile.rs | 8 +- crates/core/src/repofile/keyfile.rs | 30 +- crates/core/src/repofile/packfile.rs | 29 +- crates/core/src/repofile/snapshotfile.rs | 80 ++--- crates/core/src/repository.rs | 307 +++++++------------ crates/core/src/repository/command_input.rs | 2 +- crates/core/src/repository/warm_up.rs | 22 +- crates/core/src/vfs.rs | 26 +- crates/core/src/vfs/webdavfs.rs | 4 +- 44 files changed, 418 insertions(+), 822 deletions(-) diff --git a/crates/backend/src/choose.rs b/crates/backend/src/choose.rs index 9e246189..bc992620 100644 --- a/crates/backend/src/choose.rs +++ b/crates/backend/src/choose.rs @@ -101,7 +101,7 @@ impl BackendOptions { /// /// # Errors /// - /// If the backend cannot be loaded, an error is returned. + /// * If the backend cannot be loaded, an error is returned. /// /// # Returns /// @@ -140,9 +140,7 @@ pub trait BackendChoice { /// /// # Errors /// - /// * [`BackendAccessErrorKind::BackendNotSupported`] - If the backend is not supported. - /// - /// [`BackendAccessErrorKind::BackendNotSupported`]: crate::error::BackendAccessErrorKind::BackendNotSupported + /// * If the backend is not supported. fn to_backend( &self, location: BackendLocation, diff --git a/crates/backend/src/local.rs b/crates/backend/src/local.rs index 18b2bf81..04cbdcc5 100644 --- a/crates/backend/src/local.rs +++ b/crates/backend/src/local.rs @@ -86,7 +86,7 @@ impl LocalBackend { /// /// # Errors /// - /// + // TODO: Add error types /// /// # Returns /// @@ -173,7 +173,7 @@ impl LocalBackend { let ac = AhoCorasick::new(patterns).map_err(|err| { RusticError::with_source( - ErrorKind::Backend, + ErrorKind::Internal, "Experienced an error building AhoCorasick automaton for command replacement. This is a bug. Please report it.", err) })?; @@ -264,13 +264,9 @@ impl ReadBackend for LocalBackend { /// /// # Errors /// - /// * [`LocalBackendErrorKind::QueryingMetadataFailed`] - If the metadata of the file could not be queried. - /// * [`LocalBackendErrorKind::FromTryIntError`] - If the length of the file could not be converted to u32. - /// * [`LocalBackendErrorKind::QueryingWalkDirMetadataFailed`] - If the metadata of the file could not be queried. - /// - /// [`LocalBackendErrorKind::QueryingMetadataFailed`]: LocalBackendErrorKind::QueryingMetadataFailed - /// [`LocalBackendErrorKind::FromTryIntError`]: LocalBackendErrorKind::FromTryIntError - /// [`LocalBackendErrorKind::QueryingWalkDirMetadataFailed`]: LocalBackendErrorKind::QueryingWalkDirMetadataFailed + /// * If the metadata of the file could not be queried. + /// * If the length of the file could not be converted to u32. + /// * If the metadata of the file could not be queried. fn list_with_size(&self, tpe: FileType) -> RusticResult> { trace!("listing tpe: {tpe:?}"); let path = self.path.join(tpe.dirname()); @@ -347,8 +343,8 @@ impl ReadBackend for LocalBackend { /// /// # Errors /// - /// - If the file could not be read. - /// - If the file could not be found. + /// * If the file could not be read. + /// * If the file could not be found. fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult { trace!("reading tpe: {tpe:?}, id: {id}"); Ok(fs::read(self.path(tpe, id)) @@ -375,7 +371,7 @@ impl ReadBackend for LocalBackend { /// /// # Errors /// - /// + // TODO: Add error types fn read_partial( &self, tpe: FileType, @@ -434,9 +430,7 @@ impl WriteBackend for LocalBackend { /// /// # Errors /// - /// * [`LocalBackendErrorKind::DirectoryCreationFailed`] - If the directory could not be created. - /// - /// [`LocalBackendErrorKind::DirectoryCreationFailed`]: LocalBackendErrorKind::DirectoryCreationFailed + /// * If the directory could not be created. fn create(&self) -> RusticResult<()> { trace!("creating repo at {:?}", self.path); fs::create_dir_all(&self.path).map_err(|err| { @@ -485,6 +479,7 @@ impl WriteBackend for LocalBackend { /// /// # Errors /// + // TODO: Add error types fn write_bytes( &self, tpe: FileType, @@ -502,7 +497,7 @@ impl WriteBackend for LocalBackend { .open(&filename) .map_err(|err| { RusticError::with_source( - ErrorKind::Backend, + ErrorKind::Io, "Failed to open the file. Please check the file and try again.", err, ) @@ -511,7 +506,7 @@ impl WriteBackend for LocalBackend { file.set_len(buf.len().try_into().map_err(|err| { RusticError::with_source( - ErrorKind::Backend, + ErrorKind::Internal, "Failed to convert length to u64. This is a bug. Please report it.", err, ) @@ -519,7 +514,7 @@ impl WriteBackend for LocalBackend { })?) .map_err(|err| { RusticError::with_source( - ErrorKind::Backend, + ErrorKind::Io, "Failed to set the length of the file. Please check the file and try again.", err, ) @@ -528,7 +523,7 @@ impl WriteBackend for LocalBackend { file.write_all(&buf).map_err(|err| { RusticError::with_source( - ErrorKind::Backend, + ErrorKind::Io, "Failed to write to the buffer. Please check the file and try again.", err, ) @@ -537,7 +532,7 @@ impl WriteBackend for LocalBackend { file.sync_all().map_err(|err| { RusticError::with_source( - ErrorKind::Backend, + ErrorKind::Io, "Failed to sync OS Metadata to disk. Please check the file and try again.", err, ) @@ -562,9 +557,7 @@ impl WriteBackend for LocalBackend { /// /// # Errors /// - /// * [`LocalBackendErrorKind::FileRemovalFailed`] - If the file could not be removed. - /// - /// [`LocalBackendErrorKind::FileRemovalFailed`]: LocalBackendErrorKind::FileRemovalFailed + /// * If the file could not be removed. fn remove(&self, tpe: FileType, id: &Id, _cacheable: bool) -> RusticResult<()> { trace!("removing tpe: {:?}, id: {}", &tpe, &id); let filename = self.path(tpe, id); diff --git a/crates/backend/src/opendal.rs b/crates/backend/src/opendal.rs index 9fcd8648..f220e8bb 100644 --- a/crates/backend/src/opendal.rs +++ b/crates/backend/src/opendal.rs @@ -94,7 +94,7 @@ impl OpenDALBackend { /// /// # Errors /// - /// If the path is not a valid `OpenDAL` path, an error is returned. + /// * If the path is not a valid `OpenDAL` path. /// /// # Returns /// diff --git a/crates/backend/src/rclone.rs b/crates/backend/src/rclone.rs index ab1346ca..0f13d619 100644 --- a/crates/backend/src/rclone.rs +++ b/crates/backend/src/rclone.rs @@ -58,19 +58,12 @@ impl Drop for RcloneBackend { /// /// # Errors /// -/// * [`RcloneErrorKind::FromIoError`] - If the rclone version could not be determined. -/// * [`RcloneErrorKind::FromUtf8Error`] - If the rclone version could not be determined. -/// * [`RcloneErrorKind::NoOutputForRcloneVersion`] - If the rclone version could not be determined. -/// * [`RcloneErrorKind::FromParseVersion`] - If the rclone version could not be determined. +/// * If the rclone version could not be determined or parsed. +/// * If the rclone version is not supported. /// /// # Returns /// -/// * `Ok(())` - If the rclone version is supported. -/// -/// [`RcloneErrorKind::FromIoError`]: RcloneErrorKind::FromIoError -/// [`RcloneErrorKind::FromUtf8Error`]: RcloneErrorKind::FromUtf8Error -/// [`RcloneErrorKind::NoOutputForRcloneVersion`]: RcloneErrorKind::NoOutputForRcloneVersion -/// [`RcloneErrorKind::FromParseVersion`]: RcloneErrorKind::FromParseVersion +/// * Ok(()), if the rclone version is supported. fn check_clone_version(rclone_version_output: &[u8]) -> RusticResult<()> { let rclone_version = std::str::from_utf8(rclone_version_output) .map_err(|err| { @@ -146,7 +139,7 @@ impl RcloneBackend { /// /// # Panics /// - /// If the rclone command is not found. + /// * If the rclone command is not found. // TODO: This should be an error, not a panic. #[allow(clippy::too_many_lines)] pub fn new(url: impl AsRef, options: HashMap) -> RusticResult { @@ -208,12 +201,12 @@ impl RcloneBackend { .stderr(Stdio::piped()) .spawn() .map_err(|err| - RusticError::with_source( - ErrorKind::ExternalCommand, - "Experienced an error while running rclone. Please check if rclone is installed and working correctly.", - err - ) - .attach_context("rclone command", rclone_command.to_string()) + RusticError::with_source( + ErrorKind::ExternalCommand, + "Experienced an error while running rclone. Please check if rclone is installed and working correctly.", + err + ) + .attach_context("rclone command", rclone_command.to_string()) )?; let mut stderr = BufReader::new( diff --git a/crates/backend/src/rest.rs b/crates/backend/src/rest.rs index 2b3aaceb..63309229 100644 --- a/crates/backend/src/rest.rs +++ b/crates/backend/src/rest.rs @@ -392,9 +392,7 @@ impl ReadBackend for RestBackend { /// /// # Errors /// - /// * [`RestErrorKind::BackoffError`] - If the backoff failed. - /// - /// [`RestErrorKind::BackoffError`]: RestErrorKind::BackoffError + /// * If the backoff failed. fn read_partial( &self, tpe: FileType, diff --git a/crates/backend/src/util.rs b/crates/backend/src/util.rs index 138fb0b7..ea46edba 100644 --- a/crates/backend/src/util.rs +++ b/crates/backend/src/util.rs @@ -34,7 +34,7 @@ impl std::fmt::Display for BackendLocation { /// /// # Errors /// -/// If the url is not a valid url, an error is returned. +/// * If the url is not a valid url, an error is returned. /// /// # Returns /// diff --git a/crates/core/src/archiver.rs b/crates/core/src/archiver.rs index 627f2116..337c5465 100644 --- a/crates/core/src/archiver.rs +++ b/crates/core/src/archiver.rs @@ -82,11 +82,8 @@ impl<'a, BE: DecryptFullBackend, I: ReadGlobalIndex> Archiver<'a, BE, I> { /// /// # Errors /// - /// * [`PackerErrorKind::SendingCrossbeamMessageFailed`] - If sending the message to the raw packer fails. - /// * [`PackerErrorKind::IntConversionFailed`] - If converting the data length to u64 fails - /// - /// [`PackerErrorKind::SendingCrossbeamMessageFailed`]: crate::error::PackerErrorKind::SendingCrossbeamMessageFailed - /// [`PackerErrorKind::IntConversionFailed`]: crate::error::PackerErrorKind::IntConversionFailed + /// * If sending the message to the raw packer fails. + /// * If converting the data length to u64 fails pub fn new( be: BE, index: &'a I, @@ -131,13 +128,9 @@ impl<'a, BE: DecryptFullBackend, I: ReadGlobalIndex> Archiver<'a, BE, I> { /// /// # Errors /// - /// * [`PackerErrorKind::SendingCrossbeamMessageFailed`] - If sending the message to the raw packer fails. - /// * [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`] - If the index file could not be serialized. - /// * [`SnapshotFileErrorKind::OutOfRange`] - If the time is not in the range of `Local::now()` - /// - /// [`PackerErrorKind::SendingCrossbeamMessageFailed`]: crate::error::PackerErrorKind::SendingCrossbeamMessageFailed - /// [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`]: crate::error::CryptBackendErrorKind::SerializingToJsonByteVectorFailed - /// [`SnapshotFileErrorKind::OutOfRange`]: crate::error::SnapshotFileErrorKind::OutOfRange + /// * If sending the message to the raw packer fails. + /// * If the index file could not be serialized. + /// * If the time is not in the range of `Local::now()`. pub fn archive( mut self, src: &R, diff --git a/crates/core/src/archiver/file_archiver.rs b/crates/core/src/archiver/file_archiver.rs index 3fab2699..867f6a72 100644 --- a/crates/core/src/archiver/file_archiver.rs +++ b/crates/core/src/archiver/file_archiver.rs @@ -55,11 +55,8 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> FileArchiver<'a, BE, I> { /// /// # Errors /// - /// * [`PackerErrorKind::SendingCrossbeamMessageFailed`] - If sending the message to the raw packer fails. - /// * [`PackerErrorKind::IntConversionFailed`] - If converting the data length to u64 fails - /// - /// [`PackerErrorKind::SendingCrossbeamMessageFailed`]: crate::error::PackerErrorKind::SendingCrossbeamMessageFailed - /// [`PackerErrorKind::IntConversionFailed`]: crate::error::PackerErrorKind::IntConversionFailed + /// * If sending the message to the raw packer fails. + /// * If converting the data length to u64 fails pub fn new( be: BE, index: &'a I, @@ -98,13 +95,11 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> FileArchiver<'a, BE, I> { /// /// # Errors /// - /// [`ArchiverErrorKind::UnpackingTreeTypeOptionalFailed`] - If the item could not be unpacked. + /// * If the item could not be unpacked. /// /// # Returns /// /// The processed item. - /// - /// [`ArchiverErrorKind::UnpackingTreeTypeOptionalFailed`]: crate::error::ArchiverErrorKind::UnpackingTreeTypeOptionalFailed pub(crate) fn process( &self, item: ItemWithParent>, @@ -194,7 +189,7 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> FileArchiver<'a, BE, I> { /// /// # Panics /// - /// If the channel could not be dropped + /// * If the channel could not be dropped pub(crate) fn finalize(self) -> RusticResult { self.data_packer.finalize() } diff --git a/crates/core/src/archiver/parent.rs b/crates/core/src/archiver/parent.rs index 2f48a8ef..e0e15719 100644 --- a/crates/core/src/archiver/parent.rs +++ b/crates/core/src/archiver/parent.rs @@ -217,9 +217,7 @@ impl Parent { /// /// # Errors /// - /// * [`ArchiverErrorKind::TreeStackEmpty`] - If the tree stack is empty. - /// - /// [`ArchiverErrorKind::TreeStackEmpty`]: crate::error::ArchiverErrorKind::TreeStackEmpty + /// * If the tree stack is empty. fn finish_dir(&mut self) -> ArchiverResult<()> { let (tree, node_idx) = self.stack.pop().ok_or(ArchiverErrorKind::TreeStackEmpty)?; @@ -248,9 +246,7 @@ impl Parent { /// /// # Errors /// - /// * [`ArchiverErrorKind::TreeStackEmpty`] - If the tree stack is empty. - /// - /// [`ArchiverErrorKind::TreeStackEmpty`]: crate::error::ArchiverErrorKind::TreeStackEmpty + /// * If the tree stack is empty. pub(crate) fn process( &mut self, be: &impl DecryptReadBackend, diff --git a/crates/core/src/archiver/tree_archiver.rs b/crates/core/src/archiver/tree_archiver.rs index be806417..297af3d5 100644 --- a/crates/core/src/archiver/tree_archiver.rs +++ b/crates/core/src/archiver/tree_archiver.rs @@ -57,11 +57,8 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> TreeArchiver<'a, BE, I> { /// /// # Errors /// - /// * [`PackerErrorKind::SendingCrossbeamMessageFailed`] - If sending the message to the raw packer fails. - /// * [`PackerErrorKind::IntConversionFailed`] - If converting the data length to u64 fails - /// - /// [`PackerErrorKind::SendingCrossbeamMessageFailed`]: crate::error::PackerErrorKind::SendingCrossbeamMessageFailed - /// [`PackerErrorKind::IntConversionFailed`]: crate::error::PackerErrorKind::IntConversionFailed + /// * If sending the message to the raw packer fails. + /// * If converting the data length to u64 fails pub(crate) fn new( be: BE, index: &'a I, @@ -94,9 +91,7 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> TreeArchiver<'a, BE, I> { /// /// # Errors /// - /// * [`ArchiverErrorKind::TreeStackEmpty`] - If the tree stack is empty. - /// - /// [`ArchiverErrorKind::TreeStackEmpty`]: crate::error::ArchiverErrorKind::TreeStackEmpty + /// * If the tree stack is empty. // TODO: Add more errors! pub(crate) fn add(&mut self, item: TreeItem) -> RusticResult<()> { match item { @@ -167,13 +162,11 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> TreeArchiver<'a, BE, I> { /// /// # Errors /// - /// * [`PackerErrorKind::SendingCrossbeamMessageFailed`] - If sending the message to the raw packer fails. + /// * If sending the message to the raw packer fails. /// /// # Returns /// /// The id of the tree. - /// - /// [`PackerErrorKind::SendingCrossbeamMessageFailed`]: crate::error::PackerErrorKind::SendingCrossbeamMessageFailed fn backup_tree(&mut self, path: &Path, parent: &ParentResult) -> RusticResult { let (chunk, id) = self.tree.serialize().map_err(|err| { RusticError::with_source( @@ -219,7 +212,7 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> TreeArchiver<'a, BE, I> { /// /// # Errors /// - /// * [`PackerErrorKind::SendingCrossbeamMessageFailed`] - If sending the message to the raw packer fails. + /// * If sending the message to the raw packer fails. /// /// # Returns /// @@ -227,9 +220,7 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> TreeArchiver<'a, BE, I> { /// /// # Panics /// - /// If the channel of the tree packer is not dropped. - /// - /// [`PackerErrorKind::SendingCrossbeamMessageFailed`]: crate::error::PackerErrorKind::SendingCrossbeamMessageFailed + /// * If the channel of the tree packer is not dropped. pub(crate) fn finalize( mut self, parent_tree: Option, diff --git a/crates/core/src/backend.rs b/crates/core/src/backend.rs index 3ede1b8d..9f46dd36 100644 --- a/crates/core/src/backend.rs +++ b/crates/core/src/backend.rs @@ -27,10 +27,6 @@ use crate::{ id::Id, }; -// #[derive(thiserror::Error, Debug, displaydoc::Display)] -// /// Experienced an error in the backend: `{0}` -// pub struct BackendDynError(pub Box); - /// [`BackendErrorKind`] describes the errors that can be returned by the various Backends #[derive(thiserror::Error, Debug, displaydoc::Display)] #[non_exhaustive] @@ -159,7 +155,7 @@ pub trait ReadBackend: Send + Sync + 'static { /// /// # Errors /// - /// If the files could not be listed. + /// * If the files could not be listed. fn list_with_size(&self, tpe: FileType) -> RusticResult>; /// Lists all files of the given type. @@ -170,7 +166,7 @@ pub trait ReadBackend: Send + Sync + 'static { /// /// # Errors /// - /// If the files could not be listed. + /// * If the files could not be listed. fn list(&self, tpe: FileType) -> RusticResult> { Ok(self .list_with_size(tpe)? @@ -188,7 +184,7 @@ pub trait ReadBackend: Send + Sync + 'static { /// /// # Errors /// - /// If the file could not be read. + /// * If the file could not be read. fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult; /// Reads partial data of the given file. @@ -203,7 +199,7 @@ pub trait ReadBackend: Send + Sync + 'static { /// /// # Errors /// - /// If the file could not be read. + /// * If the file could not be read. fn read_partial( &self, tpe: FileType, @@ -227,7 +223,7 @@ pub trait ReadBackend: Send + Sync + 'static { /// /// # Errors /// - /// If the file could not be read. + /// * If the file could not be read. fn warm_up(&self, _tpe: FileType, _id: &Id) -> RusticResult<()> { Ok(()) } @@ -254,15 +250,12 @@ pub trait FindInBackend: ReadBackend { /// /// # Errors /// - /// * [`BackendAccessErrorKind::NoSuitableIdFound`] - If no id could be found. - /// * [`BackendAccessErrorKind::IdNotUnique`] - If the id is not unique. + /// * If no id could be found. + /// * If the id is not unique. /// /// # Note /// /// This function is used to find the id of a snapshot. - /// - /// [`BackendAccessErrorKind::NoSuitableIdFound`]: crate::error::BackendAccessErrorKind::NoSuitableIdFound - /// [`BackendAccessErrorKind::IdNotUnique`]: crate::error::BackendAccessErrorKind::IdNotUnique fn find_starts_with>(&self, tpe: FileType, vec: &[T]) -> RusticResult> { #[derive(Clone, Copy, PartialEq, Eq)] enum MapResult { @@ -309,13 +302,9 @@ pub trait FindInBackend: ReadBackend { /// /// # Errors /// - /// * [`IdErrorKind::HexError`] - If the string is not a valid hexadecimal string - /// * [`BackendAccessErrorKind::NoSuitableIdFound`] - If no id could be found. - /// * [`BackendAccessErrorKind::IdNotUnique`] - If the id is not unique. - /// - /// [`IdErrorKind::HexError`]: crate::error::IdErrorKind::HexError - /// [`BackendAccessErrorKind::NoSuitableIdFound`]: crate::error::BackendAccessErrorKind::NoSuitableIdFound - /// [`BackendAccessErrorKind::IdNotUnique`]: crate::error::BackendAccessErrorKind::IdNotUnique + /// * If the string is not a valid hexadecimal string + /// * If no id could be found. + /// * If the id is not unique. fn find_id(&self, tpe: FileType, id: &str) -> RusticResult { Ok(self.find_ids(tpe, &[id.to_string()])?.remove(0)) } @@ -333,13 +322,9 @@ pub trait FindInBackend: ReadBackend { /// /// # Errors /// - /// * [`IdErrorKind::HexError`] - If the string is not a valid hexadecimal string - /// * [`BackendAccessErrorKind::NoSuitableIdFound`] - If no id could be found. - /// * [`BackendAccessErrorKind::IdNotUnique`] - If the id is not unique. - /// - /// [`IdErrorKind::HexError`]: crate::error::IdErrorKind::HexError - /// [`BackendAccessErrorKind::NoSuitableIdFound`]: crate::error::BackendAccessErrorKind::NoSuitableIdFound - /// [`BackendAccessErrorKind::IdNotUnique`]: crate::error::BackendAccessErrorKind::IdNotUnique + /// * If the string is not a valid hexadecimal string + /// * If no id could be found. + /// * If the id is not unique. fn find_ids>(&self, tpe: FileType, ids: &[T]) -> RusticResult> { ids.iter() .map(|id| id.as_ref().parse()) @@ -359,7 +344,7 @@ pub trait WriteBackend: ReadBackend { /// /// # Errors /// - /// If the backend could not be created. + /// * If the backend could not be created. /// /// # Returns /// @@ -379,7 +364,7 @@ pub trait WriteBackend: ReadBackend { /// /// # Errors /// - /// If the data could not be written. + /// * If the data could not be written. /// /// # Returns /// @@ -396,7 +381,7 @@ pub trait WriteBackend: ReadBackend { /// /// # Errors /// - /// If the file could not be removed. + /// * If the file could not be removed. /// /// # Returns /// @@ -512,7 +497,7 @@ pub trait ReadSourceOpen { /// /// # Errors /// - /// If the source could not be opened. + /// * If the source could not be opened. /// /// # Result /// @@ -541,7 +526,7 @@ pub trait ReadSource: Sync + Send { /// /// # Errors /// - /// If the size could not be determined. + /// * If the size could not be determined. /// /// # Returns /// diff --git a/crates/core/src/backend/cache.rs b/crates/core/src/backend/cache.rs index 96a75c41..0fc246ca 100644 --- a/crates/core/src/backend/cache.rs +++ b/crates/core/src/backend/cache.rs @@ -59,7 +59,7 @@ impl ReadBackend for CachedBackend { /// /// # Errors /// - /// If the backend does not support listing files. + /// * If the backend does not support listing files. /// /// # Returns /// @@ -85,13 +85,11 @@ impl ReadBackend for CachedBackend { /// /// # Errors /// - /// * [`CacheBackendErrorKind::FromIoError`] - If the file could not be read. + /// * If the file could not be read. /// /// # Returns /// /// The data read. - /// - /// [`CacheBackendErrorKind::FromIoError`]: crate::error::CacheBackendErrorKind::FromIoError fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult { if tpe.is_cacheable() { match self.cache.read_full(tpe, id) { @@ -123,13 +121,11 @@ impl ReadBackend for CachedBackend { /// /// # Errors /// - /// * [`CacheBackendErrorKind::FromIoError`] - If the file could not be read. + /// * If the file could not be read. /// /// # Returns /// /// The data read. - /// - /// [`CacheBackendErrorKind::FromIoError`]: crate::error::CacheBackendErrorKind::FromIoError fn read_partial( &self, tpe: FileType, @@ -230,11 +226,8 @@ impl Cache { /// /// # Errors /// - /// * [`CacheBackendErrorKind::NoCacheDirectory`] - If no path is given and the default cache directory could not be determined. - /// * [`CacheBackendErrorKind::FromIoError`] - If the cache directory could not be created. - /// - /// [`CacheBackendErrorKind::NoCacheDirectory`]: crate::error::CacheBackendErrorKind::NoCacheDirectory - /// [`CacheBackendErrorKind::FromIoError`]: crate::error::CacheBackendErrorKind::FromIoError + /// * If no path is given and the default cache directory could not be determined. + /// * If the cache directory could not be created. pub fn new(id: RepositoryId, path: Option) -> RusticResult { let mut path = if let Some(p) = path { p @@ -280,7 +273,7 @@ impl Cache { /// /// # Panics /// - /// Panics if the path is not valid unicode. + /// * Panics if the path is not valid unicode. // TODO: Does this need to panic? Result? #[must_use] pub fn location(&self) -> &str { @@ -322,11 +315,8 @@ impl Cache { /// /// # Errors /// - /// * [`CacheBackendErrorKind::FromIoError`] - If the cache directory could not be read. - /// * [`IdErrorKind::HexError`] - If the string is not a valid hexadecimal string - /// - /// [`CacheBackendErrorKind::FromIoError`]: crate::error::CacheBackendErrorKind::FromIoError - /// [`IdErrorKind::HexError`]: crate::error::IdErrorKind::HexError + /// * If the cache directory could not be read. + /// * If the string is not a valid hexadecimal string #[allow(clippy::unnecessary_wraps)] pub fn list_with_size(&self, tpe: FileType) -> RusticResult> { let path = self.path.join(tpe.dirname()); @@ -364,9 +354,7 @@ impl Cache { /// /// # Errors /// - /// * [`CacheBackendErrorKind::FromIoError`] - If the cache directory could not be read. - /// - /// [`CacheBackendErrorKind::FromIoError`]: crate::error::CacheBackendErrorKind::FromIoError + /// * If the cache directory could not be read. pub fn remove_not_in_list(&self, tpe: FileType, list: &Vec<(Id, u32)>) -> RusticResult<()> { let mut list_cache = self.list_with_size(tpe)?; // remove present files from the cache list @@ -394,9 +382,7 @@ impl Cache { /// /// # Errors /// - /// * [`CacheBackendErrorKind::FromIoError`] - If the file could not be read. - /// - /// [`CacheBackendErrorKind::FromIoError`]: crate::error::CacheBackendErrorKind::FromIoError + /// * If the file could not be read. pub fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult> { trace!("cache reading tpe: {:?}, id: {}", &tpe, &id); @@ -430,9 +416,7 @@ impl Cache { /// /// # Errors /// - /// * [`CacheBackendErrorKind::FromIoError`] - If the file could not be read. - /// - /// [`CacheBackendErrorKind::FromIoError`]: crate::error::CacheBackendErrorKind::FromIoError + /// * If the file could not be read. pub fn read_partial( &self, tpe: FileType, @@ -493,9 +477,7 @@ impl Cache { /// /// # Errors /// - /// * [`CacheBackendErrorKind::FromIoError`] - If the file could not be written. - /// - /// [`CacheBackendErrorKind::FromIoError`]: crate::error::CacheBackendErrorKind::FromIoError + /// * If the file could not be written. pub fn write_bytes(&self, tpe: FileType, id: &Id, buf: &Bytes) -> RusticResult<()> { trace!("cache writing tpe: {:?}, id: {}", &tpe, &id); @@ -539,9 +521,7 @@ impl Cache { /// /// # Errors /// - /// * [`CacheBackendErrorKind::FromIoError`] - If the file could not be removed. - /// - /// [`CacheBackendErrorKind::FromIoError`]: crate::error::CacheBackendErrorKind::FromIoError + /// * If the file could not be removed. pub fn remove(&self, tpe: FileType, id: &Id) -> RusticResult<()> { trace!("cache writing tpe: {:?}, id: {}", &tpe, &id); let filename = self.path(tpe, id); diff --git a/crates/core/src/backend/decrypt.rs b/crates/core/src/backend/decrypt.rs index 665b349a..ee0e0c61 100644 --- a/crates/core/src/backend/decrypt.rs +++ b/crates/core/src/backend/decrypt.rs @@ -41,7 +41,7 @@ pub trait DecryptReadBackend: ReadBackend + Clone + 'static { /// /// # Errors /// - /// If the data could not be decrypted. + /// * If the data could not be decrypted. fn decrypt(&self, data: &[u8]) -> RusticResult>; /// Reads the given file. @@ -53,7 +53,7 @@ pub trait DecryptReadBackend: ReadBackend + Clone + 'static { /// /// # Errors /// - /// If the file could not be read. + /// * If the file could not be read. fn read_encrypted_full(&self, tpe: FileType, id: &Id) -> RusticResult; /// Reads the given file from partial data. @@ -65,11 +65,8 @@ pub trait DecryptReadBackend: ReadBackend + Clone + 'static { /// /// # Errors /// - /// * [`CryptBackendErrorKind::DecodingZstdCompressedDataFailed`] - If the data could not be decoded. - /// * [`CryptBackendErrorKind::LengthOfUncompressedDataDoesNotMatch`] - If the length of the uncompressed data does not match the given length. - /// - /// [`CryptBackendErrorKind::DecodingZstdCompressedDataFailed`]: crate::error::CryptBackendErrorKind::DecodingZstdCompressedDataFailed - /// [`CryptBackendErrorKind::LengthOfUncompressedDataDoesNotMatch`]: crate::error::CryptBackendErrorKind::LengthOfUncompressedDataDoesNotMatch + /// * If the data could not be decoded. + /// * If the length of the uncompressed data does not match the given length. fn read_encrypted_from_partial( &self, data: &[u8], @@ -109,7 +106,7 @@ pub trait DecryptReadBackend: ReadBackend + Clone + 'static { /// /// # Errors /// - /// If the file could not be read. + /// * If the file could not be read. fn read_encrypted_partial( &self, tpe: FileType, @@ -133,7 +130,7 @@ pub trait DecryptReadBackend: ReadBackend + Clone + 'static { /// /// # Errors /// - /// If the file could not be read. + /// * If the file could not be read. fn get_file(&self, id: &Id) -> RusticResult { let data = self.read_encrypted_full(F::TYPE, id)?; let deserialized = serde_json::from_slice(&data).map_err(|err| { @@ -205,7 +202,7 @@ pub trait DecryptWriteBackend: WriteBackend + Clone + 'static { /// /// # Errors /// - /// If the data could not be written. + /// * If the data could not be written. /// /// # Returns /// @@ -229,7 +226,7 @@ pub trait DecryptWriteBackend: WriteBackend + Clone + 'static { /// /// # Errors /// - /// If the data could not be written. + /// * If the data could not be written. /// /// # Returns /// @@ -248,13 +245,11 @@ pub trait DecryptWriteBackend: WriteBackend + Clone + 'static { /// /// # Errors /// - /// * [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`] - If the file could not be serialized to json. + /// * If the file could not be serialized to json. /// /// # Returns /// /// The id of the file. - /// - /// [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`]: crate::error::CryptBackendErrorKind::SerializingToJsonByteVectorFailed fn save_file(&self, file: &F) -> RusticResult { let data = serde_json::to_vec(file).map_err(|err| { RusticError::with_source( @@ -275,13 +270,11 @@ pub trait DecryptWriteBackend: WriteBackend + Clone + 'static { /// /// # Errors /// - /// * [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`] - If the file could not be serialized to json. + /// * If the file could not be serialized to json. /// /// # Returns /// /// The id of the file. - /// - /// [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`]: crate::error::CryptBackendErrorKind::SerializingToJsonByteVectorFailed fn save_file_uncompressed(&self, file: &F) -> RusticResult { let data = serde_json::to_vec(file).map_err(|err| { RusticError::with_source( @@ -303,7 +296,7 @@ pub trait DecryptWriteBackend: WriteBackend + Clone + 'static { /// /// # Errors /// - /// * [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`] - If the file could not be serialized to json. + /// * If the file could not be serialized to json. fn save_list<'a, F: RepoFile, I: ExactSizeIterator + Send>( &self, list: I, @@ -327,10 +320,6 @@ pub trait DecryptWriteBackend: WriteBackend + Clone + 'static { /// * `cacheable` - Whether the files should be cached. /// * `list` - The list of files to delete. /// * `p` - The progress bar. - /// - /// # Panics - /// - /// If the files could not be deleted. fn delete_list<'a, ID: RepoId, I: ExactSizeIterator + Send>( &self, cacheable: bool, @@ -339,8 +328,7 @@ pub trait DecryptWriteBackend: WriteBackend + Clone + 'static { ) -> RusticResult<()> { p.set_length(list.len() as u64); list.par_bridge().try_for_each(|id| -> RusticResult<_> { - // TODO: Don't panic on file not being able to be deleted. - self.remove(ID::TYPE, id, cacheable).unwrap(); + self.remove(ID::TYPE, id, cacheable)?; p.inc(1); Ok(()) })?; @@ -531,13 +519,11 @@ impl DecryptWriteBackend for DecryptBackend { /// /// # Errors /// - /// * [`CryptBackendErrorKind::CopyEncodingDataFailed`] - If the data could not be encoded. + /// * If the data could not be encoded. /// /// # Returns /// /// The id of the data. - /// - /// [`CryptBackendErrorKind::CopyEncodingDataFailed`]: crate::error::CryptBackendErrorKind::CopyEncodingDataFailed fn hash_write_full(&self, tpe: FileType, data: &[u8]) -> RusticResult { let data_encrypted = self.encrypt_file(data)?; @@ -599,11 +585,8 @@ impl DecryptReadBackend for DecryptBackend { /// /// # Errors /// - /// * [`CryptBackendErrorKind::DecryptionNotSupportedForBackend`] - If the backend does not support decryption. - /// * [`CryptBackendErrorKind::DecodingZstdCompressedDataFailed`] - If the data could not be decoded. - /// - /// [`CryptBackendErrorKind::DecryptionNotSupportedForBackend`]: crate::error::CryptBackendErrorKind::DecryptionNotSupportedForBackend - /// [`CryptBackendErrorKind::DecodingZstdCompressedDataFailed`]: crate::error::CryptBackendErrorKind::DecodingZstdCompressedDataFailed + /// * If the backend does not support decryption. + /// * If the data could not be decoded. fn read_encrypted_full(&self, tpe: FileType, id: &Id) -> RusticResult { self.decrypt_file(&self.read_full(tpe, id)?).map(Into::into) } diff --git a/crates/core/src/backend/dry_run.rs b/crates/core/src/backend/dry_run.rs index 5b2f60f4..5bd07cb8 100644 --- a/crates/core/src/backend/dry_run.rs +++ b/crates/core/src/backend/dry_run.rs @@ -49,15 +49,12 @@ impl DecryptReadBackend for DryRunBackend { /// /// # Errors /// - /// * [`CryptBackendErrorKind::DecryptionNotSupportedForBackend`] - If the backend does not support decryption. - /// * [`CryptBackendErrorKind::DecodingZstdCompressedDataFailed`] - If decoding the zstd compressed data failed. + /// * If the backend does not support decryption. + /// * If decoding the zstd compressed data failed. /// /// # Returns /// /// The data read. - /// - /// [`CryptBackendErrorKind::DecryptionNotSupportedForBackend`]: crate::error::CryptBackendErrorKind::DecryptionNotSupportedForBackend - /// [`CryptBackendErrorKind::DecodingZstdCompressedDataFailed`]: crate::error::CryptBackendErrorKind::DecodingZstdCompressedDataFailed fn read_encrypted_full(&self, tpe: FileType, id: &Id) -> RusticResult { let decrypted = self.decrypt(&self.read_full(tpe, id)?)?; Ok(match decrypted.first() { diff --git a/crates/core/src/backend/ignore.rs b/crates/core/src/backend/ignore.rs index b1d569e8..0e8d8048 100644 --- a/crates/core/src/backend/ignore.rs +++ b/crates/core/src/backend/ignore.rs @@ -335,9 +335,7 @@ impl ReadSourceOpen for OpenFile { /// /// # Errors /// - /// * [`IgnoreErrorKind::UnableToOpenFile`] - If the file could not be opened. - /// - /// [`IgnoreErrorKind::UnableToOpenFile`]: crate::error::IgnoreErrorKind::UnableToOpenFile + /// * If the file could not be opened. fn open(self) -> RusticResult { let path = self.0; File::open(&path).map_err(|err| { @@ -363,7 +361,7 @@ impl ReadSource for LocalSource { /// /// # Errors /// - /// If the size could not be determined. + /// * If the size could not be determined. fn size(&self) -> RusticResult> { let mut size = 0; for entry in self.builder.build() { @@ -442,11 +440,8 @@ impl Iterator for LocalSourceWalker { /// /// # Errors /// -/// * [`IgnoreErrorKind::GenericError`] - If metadata could not be read. -/// * [`IgnoreErrorKind::FromIoError`] - If path of the entry could not be read. -/// -/// [`IgnoreErrorKind::GenericError`]: crate::error::IgnoreErrorKind::GenericError -/// [`IgnoreErrorKind::FromIoError`]: crate::error::IgnoreErrorKind::FromIoError +/// * If metadata could not be read. +/// * If path of the entry could not be read. #[cfg(windows)] #[allow(clippy::similar_names)] fn map_entry( @@ -580,7 +575,7 @@ fn list_extended_attributes(path: &Path) -> IgnoreResult> /// /// # Errors /// -/// * [`IgnoreErrorKind::ErrorXattr`] - if Xattr couldn't be listed or couldn't be read +/// * If Xattr couldn't be listed or couldn't be read #[cfg(all(not(windows), not(target_os = "openbsd")))] fn list_extended_attributes(path: &Path) -> IgnoreResult> { xattr::list(path) @@ -610,11 +605,8 @@ fn list_extended_attributes(path: &Path) -> IgnoreResult> /// /// # Errors /// -/// * [`IgnoreErrorKind::GenericError`] - If metadata could not be read. -/// * [`IgnoreErrorKind::FromIoError`] - If the xattr of the entry could not be read. -/// -/// [`IgnoreErrorKind::GenericError`]: crate::error::IgnoreErrorKind::GenericError -/// [`IgnoreErrorKind::FromIoError`]: crate::error::IgnoreErrorKind::FromIoError +/// * If metadata could not be read. +/// * If the xattr of the entry could not be read. #[cfg(not(windows))] // map_entry: turn entry into (Path, Node) #[allow(clippy::similar_names)] diff --git a/crates/core/src/backend/local_destination.rs b/crates/core/src/backend/local_destination.rs index 829b32b7..69627bf2 100644 --- a/crates/core/src/backend/local_destination.rs +++ b/crates/core/src/backend/local_destination.rs @@ -143,9 +143,7 @@ impl LocalDestination { /// /// # Errors /// - /// * [`LocalDestinationErrorKind::DirectoryCreationFailed`] - If the directory could not be created. - /// - /// [`LocalDestinationErrorKind::DirectoryCreationFailed`]: crate::error::LocalDestinationErrorKind::DirectoryCreationFailed + /// * If the directory could not be created. // TODO: We should use `impl Into` here. we even use it in the body! pub fn new(path: &str, create: bool, expect_file: bool) -> RusticResult { let is_dir = path.ends_with('/'); @@ -210,13 +208,11 @@ impl LocalDestination { /// /// # Errors /// - /// * [`LocalDestinationErrorKind::DirectoryRemovalFailed`] - If the directory could not be removed. + /// * If the directory could not be removed. /// /// # Notes /// /// This will remove the directory recursively. - /// - /// [`LocalDestinationErrorKind::DirectoryRemovalFailed`]: crate::error::LocalDestinationErrorKind::DirectoryRemovalFailed #[allow(clippy::unused_self)] pub(crate) fn remove_dir(&self, dirname: impl AsRef) -> LocalDestinationResult<()> { fs::remove_dir_all(dirname).map_err(LocalDestinationErrorKind::DirectoryRemovalFailed) @@ -230,7 +226,7 @@ impl LocalDestination { /// /// # Errors /// - /// * [`LocalDestinationErrorKind::FileRemovalFailed`] - If the file could not be removed. + /// * If the file could not be removed. /// /// # Notes /// @@ -238,8 +234,6 @@ impl LocalDestination { /// /// * If the file is a symlink, the symlink will be removed, not the file it points to. /// * If the file is a directory or device, this will fail. - /// - /// [`LocalDestinationErrorKind::FileRemovalFailed`]: crate::error::LocalDestinationErrorKind::FileRemovalFailed #[allow(clippy::unused_self)] pub(crate) fn remove_file(&self, filename: impl AsRef) -> LocalDestinationResult<()> { fs::remove_file(filename).map_err(LocalDestinationErrorKind::FileRemovalFailed) @@ -253,13 +247,11 @@ impl LocalDestination { /// /// # Errors /// - /// * [`LocalDestinationErrorKind::DirectoryCreationFailed`] - If the directory could not be created. + /// * If the directory could not be created. /// /// # Notes /// /// This will create the directory structure recursively. - /// - /// [`LocalDestinationErrorKind::DirectoryCreationFailed`]: crate::error::LocalDestinationErrorKind::DirectoryCreationFailed pub(crate) fn create_dir(&self, item: impl AsRef) -> LocalDestinationResult<()> { let dirname = self.path.join(item); fs::create_dir_all(dirname).map_err(LocalDestinationErrorKind::DirectoryCreationFailed)?; @@ -275,9 +267,7 @@ impl LocalDestination { /// /// # Errors /// - /// * [`LocalDestinationErrorKind::SettingTimeMetadataFailed`] - If the times could not be set - /// - /// [`LocalDestinationErrorKind::SettingTimeMetadataFailed`]: crate::error::LocalDestinationErrorKind::SettingTimeMetadataFailed + /// * If the times could not be set pub(crate) fn set_times( &self, item: impl AsRef, @@ -308,7 +298,8 @@ impl LocalDestination { /// /// # Errors /// - /// If the user/group could not be set. + /// * If the user/group could not be set. + #[allow(clippy::unused_self)] pub(crate) fn set_user_group( &self, _item: impl AsRef, @@ -330,9 +321,7 @@ impl LocalDestination { /// /// # Errors /// - /// * [`LocalDestinationErrorKind::FromErrnoError`] - If the user/group could not be set. - /// - /// [`LocalDestinationErrorKind::FromErrnoError`]: crate::error::LocalDestinationErrorKind::FromErrnoError + /// * If the user/group could not be set. #[allow(clippy::similar_names)] pub(crate) fn set_user_group( &self, @@ -365,7 +354,8 @@ impl LocalDestination { /// /// # Errors /// - /// If the uid/gid could not be set. + /// * If the uid/gid could not be set. + #[allow(clippy::unused_self)] pub(crate) fn set_uid_gid( &self, _item: impl AsRef, @@ -384,9 +374,7 @@ impl LocalDestination { /// /// # Errors /// - /// * [`LocalDestinationErrorKind::FromErrnoError`] - If the uid/gid could not be set. - /// - /// [`LocalDestinationErrorKind::FromErrnoError`]: crate::error::LocalDestinationErrorKind::FromErrnoError + /// * If the uid/gid could not be set. #[allow(clippy::similar_names)] pub(crate) fn set_uid_gid( &self, @@ -414,7 +402,8 @@ impl LocalDestination { /// /// # Errors /// - /// If the permissions could not be set. + /// * If the permissions could not be set. + #[allow(clippy::unused_self)] pub(crate) fn set_permission( &self, _item: impl AsRef, @@ -433,9 +422,7 @@ impl LocalDestination { /// /// # Errors /// - /// * [`LocalDestinationErrorKind::SettingFilePermissionsFailed`] - If the permissions could not be set. - /// - /// [`LocalDestinationErrorKind::SettingFilePermissionsFailed`]: crate::error::LocalDestinationErrorKind::SettingFilePermissionsFailed + /// * If the permissions could not be set. #[allow(clippy::similar_names)] pub(crate) fn set_permission( &self, @@ -468,7 +455,8 @@ impl LocalDestination { /// /// # Errors /// - /// If the extended attributes could not be set. + /// * If the extended attributes could not be set. + #[allow(clippy::unused_self)] pub(crate) fn set_extended_attributes( &self, _item: impl AsRef, @@ -487,13 +475,9 @@ impl LocalDestination { /// /// # Errors /// - /// * [`LocalDestinationErrorKind::ListingXattrsFailed`] - If listing the extended attributes failed. - /// * [`LocalDestinationErrorKind::GettingXattrFailed`] - If getting an extended attribute failed. - /// * [`LocalDestinationErrorKind::SettingXattrFailed`] - If setting an extended attribute failed. - /// - /// [`LocalDestinationErrorKind::ListingXattrsFailed`]: crate::error::LocalDestinationErrorKind::ListingXattrsFailed - /// [`LocalDestinationErrorKind::GettingXattrFailed`]: crate::error::LocalDestinationErrorKind::GettingXattrFailed - /// [`LocalDestinationErrorKind::SettingXattrFailed`]: crate::error::LocalDestinationErrorKind::SettingXattrFailed + /// * If listing the extended attributes failed. + /// * If getting an extended attribute failed. + /// * If setting an extended attribute failed. /// /// # Returns /// @@ -501,7 +485,7 @@ impl LocalDestination { /// /// # Panics /// - /// If the extended attributes could not be set. + /// * If the extended attributes could not be set. pub(crate) fn set_extended_attributes( &self, item: impl AsRef, @@ -569,20 +553,15 @@ impl LocalDestination { /// /// # Errors /// - /// * [`LocalDestinationErrorKind::FileDoesNotHaveParent`] - If the file does not have a parent. - /// * [`LocalDestinationErrorKind::DirectoryCreationFailed`] - If the directory could not be created. - /// * [`LocalDestinationErrorKind::OpeningFileFailed`] - If the file could not be opened. - /// * [`LocalDestinationErrorKind::SettingFileLengthFailed`] - If the length of the file could not be set. + /// * If the file does not have a parent. + /// * If the directory could not be created. + /// * If the file could not be opened. + /// * If the length of the file could not be set. /// /// # Notes /// /// If the file exists, truncate it to the given length. (TODO: check if this is correct) /// If it doesn't exist, create a new (empty) one with given length. - /// - /// [`LocalDestinationErrorKind::FileDoesNotHaveParent`]: crate::error::LocalDestinationErrorKind::FileDoesNotHaveParent - /// [`LocalDestinationErrorKind::DirectoryCreationFailed`]: crate::error::LocalDestinationErrorKind::DirectoryCreationFailed - /// [`LocalDestinationErrorKind::OpeningFileFailed`]: crate::error::LocalDestinationErrorKind::OpeningFileFailed - /// [`LocalDestinationErrorKind::SettingFileLengthFailed`]: crate::error::LocalDestinationErrorKind::SettingFileLengthFailed pub(crate) fn set_length( &self, item: impl AsRef, @@ -616,7 +595,7 @@ impl LocalDestination { /// /// # Errors /// - /// If the special file could not be created. + /// * If the special file could not be created. /// /// # Returns /// @@ -639,13 +618,9 @@ impl LocalDestination { /// /// # Errors /// - /// * [`LocalDestinationErrorKind::SymlinkingFailed`] - If the symlink could not be created. - /// * [`LocalDestinationErrorKind::DeviceIdConversionFailed`] - If the device could not be converted to the correct type. - /// * [`LocalDestinationErrorKind::FromErrnoError`] - If the device could not be created. - /// - /// [`LocalDestinationErrorKind::SymlinkingFailed`]: LocalDestinationErrorKind::SymlinkingFailed - /// [`LocalDestinationErrorKind::DeviceIdConversionFailed`]: LocalDestinationErrorKind::DeviceIdConversionFailed - /// [`LocalDestinationErrorKind::FromErrnoError`]: LocalDestinationErrorKind::FromErrnoError + /// * If the symlink could not be created. + /// * If the device could not be converted to the correct type. + /// * If the device could not be created. pub(crate) fn create_special( &self, item: impl AsRef, @@ -739,15 +714,10 @@ impl LocalDestination { /// /// # Errors /// - /// * [`LocalDestinationErrorKind::OpeningFileFailed`] - If the file could not be opened. - /// * [`LocalDestinationErrorKind::CouldNotSeekToPositionInFile`] - If the file could not be sought to the given position. - /// * [`LocalDestinationErrorKind::LengthConversionFailed`] - If the length of the file could not be converted to u32. - /// * [`LocalDestinationErrorKind::ReadingExactLengthOfFileFailed`] - If the length of the file could not be read. - /// - /// [`LocalDestinationErrorKind::OpeningFileFailed`]: LocalDestinationErrorKind::OpeningFileFailed - /// [`LocalDestinationErrorKind::CouldNotSeekToPositionInFile`]: LocalDestinationErrorKind::CouldNotSeekToPositionInFile - /// [`LocalDestinationErrorKind::LengthConversionFailed`]: LocalDestinationErrorKind::LengthConversionFailed - /// [`LocalDestinationErrorKind::ReadingExactLengthOfFileFailed`]: LocalDestinationErrorKind::ReadingExactLengthOfFileFailed + /// * If the file could not be opened. + /// * If the file could not be sought to the given position. + /// * If the length of the file could not be converted to u32. + /// * If the length of the file could not be read. pub(crate) fn read_at( &self, item: impl AsRef, @@ -810,17 +780,13 @@ impl LocalDestination { /// /// # Errors /// - /// * [`LocalDestinationErrorKind::OpeningFileFailed`] - If the file could not be opened. - /// * [`LocalDestinationErrorKind::CouldNotSeekToPositionInFile`] - If the file could not be sought to the given position. - /// * [`LocalDestinationErrorKind::CouldNotWriteToBuffer`] - If the bytes could not be written to the file. + /// * If the file could not be opened. + /// * If the file could not be sought to the given position. + /// * If the bytes could not be written to the file. /// /// # Notes /// /// This will create the file if it doesn't exist. - /// - /// [`LocalDestinationErrorKind::OpeningFileFailed`]: crate::error::LocalDestinationErrorKind::OpeningFileFailed - /// [`LocalDestinationErrorKind::CouldNotSeekToPositionInFile`]: crate::error::LocalDestinationErrorKind::CouldNotSeekToPositionInFile - /// [`LocalDestinationErrorKind::CouldNotWriteToBuffer`]: crate::error::LocalDestinationErrorKind::CouldNotWriteToBuffer pub(crate) fn write_at( &self, item: impl AsRef, diff --git a/crates/core/src/backend/node.rs b/crates/core/src/backend/node.rs index d6d40cd9..1c6635d5 100644 --- a/crates/core/src/backend/node.rs +++ b/crates/core/src/backend/node.rs @@ -94,8 +94,8 @@ pub struct Node { /// /// # Warning /// - /// This contains an escaped variant of the name in order to handle non-unicode filenames. - /// Don't access this field directly, use the [`Node::name()`] method instead! + /// * This contains an escaped variant of the name in order to handle non-unicode filenames. + /// * Don't access this field directly, use the [`Node::name()`] method instead! pub name: String, #[serde(flatten)] /// Information about node type @@ -137,8 +137,8 @@ pub enum NodeType { /// /// # Warning /// - /// This contains the target only if it is a valid unicode target. - /// Don't access this field directly, use the [`NodeType::to_link()`] method instead! + /// * This contains the target only if it is a valid unicode target. + /// * Don't access this field directly, use the [`NodeType::to_link()`] method instead! linktarget: String, #[serde_as(as = "DefaultOnNull>>")] #[serde(default, skip_serializing_if = "Option::is_none")] @@ -206,7 +206,7 @@ impl NodeType { /// /// # Panics /// - /// If called on a non-symlink node + /// * If called on a non-symlink node #[cfg(not(windows))] #[must_use] pub fn to_link(&self) -> &Path { @@ -226,7 +226,7 @@ impl NodeType { /// /// # Warning /// - /// Must be only called on `NodeType::Symlink`! + /// * Must be only called on `NodeType::Symlink`! /// /// # Panics /// @@ -358,7 +358,7 @@ impl Node { /// /// # Panics /// - /// If the name is not valid unicode + /// * If the name is not valid unicode pub fn name(&self) -> OsString { unescape_filename(&self.name).unwrap_or_else(|_| OsString::from_str(&self.name).unwrap()) } diff --git a/crates/core/src/blob/packer.rs b/crates/core/src/blob/packer.rs index 398595fd..abebf013 100644 --- a/crates/core/src/blob/packer.rs +++ b/crates/core/src/blob/packer.rs @@ -172,7 +172,7 @@ impl PackSizer { /// /// # Panics /// - /// If the size is too large + /// * If the size is too large fn add_size(&mut self, added: u32) { self.current_size += u64::from(added); } @@ -216,11 +216,8 @@ impl Packer { /// /// # Errors /// - /// * [`PackerErrorKind::SendingCrossbeamMessageFailed`] - If sending the message to the raw packer fails. - /// * [`PackerErrorKind::IntConversionFailed`] - If converting the data length to u64 fails - /// - /// [`PackerErrorKind::SendingCrossbeamMessageFailed`]: crate::error::PackerErrorKind::SendingCrossbeamMessageFailed - /// [`PackerErrorKind::IntConversionFailed`]: crate::error::PackerErrorKind::IntConversionFailed + /// * If sending the message to the raw packer fails. + /// * If converting the data length to u64 fails #[allow(clippy::unnecessary_wraps)] pub fn new( be: BE, @@ -302,9 +299,7 @@ impl Packer { /// /// # Errors /// - /// * [`PackerErrorKind::SendingCrossbeamMessageFailed`] - If sending the message to the raw packer fails. - /// - /// [`PackerErrorKind::SendingCrossbeamMessageFailed`]: crate::error::PackerErrorKind::SendingCrossbeamMessageFailed + /// * If sending the message to the raw packer fails. pub fn add(&self, data: Bytes, id: BlobId) -> RusticResult<()> { // compute size limit based on total size and size bounds self.add_with_sizelimit(data, id, None).map_err(|err| { @@ -327,9 +322,7 @@ impl Packer { /// /// # Errors /// - /// * [`PackerErrorKind::SendingCrossbeamMessageFailed`] - If sending the message to the raw packer fails. - /// - /// [`PackerErrorKind::SendingCrossbeamMessageFailed`]: crate::error::PackerErrorKind::SendingCrossbeamMessageFailed + /// * If sending the message to the raw packer fails. fn add_with_sizelimit( &self, data: Bytes, @@ -359,8 +352,8 @@ impl Packer { /// /// # Errors /// - /// If the blob is already present in the index - /// If sending the message to the raw packer fails. + /// * If the blob is already present in the index + /// * If sending the message to the raw packer fails. fn add_raw( &self, data: &[u8], @@ -387,7 +380,7 @@ impl Packer { /// /// # Panics /// - /// If the channel could not be dropped + /// * If the channel could not be dropped pub fn finalize(self) -> RusticResult { // cancel channel drop(self.sender); @@ -419,7 +412,7 @@ impl PackerStats { /// /// # Panics /// - /// If the blob type is invalid + /// * If the blob type is invalid pub fn apply(self, summary: &mut SnapshotSummary, tpe: BlobType) { summary.data_added += self.data; summary.data_added_packed += self.data_packed; @@ -518,7 +511,7 @@ impl RawPacker { /// /// # Errors /// - /// If the packfile could not be saved + /// * If the packfile could not be saved fn finalize(&mut self) -> RusticResult { self.save().map_err(|err| { RusticError::with_source( @@ -568,9 +561,7 @@ impl RawPacker { /// /// # Errors /// - /// * [`PackerErrorKind::IntConversionFailed`] - If converting the data length to u64 fails - /// - /// [`PackerErrorKind::IntConversionFailed`]: crate::error::PackerErrorKind::IntConversionFailed + /// * If converting the data length to u64 fails fn add_raw( &mut self, data: &[u8], @@ -640,11 +631,8 @@ impl RawPacker { /// /// # Errors /// - /// * [`PackerErrorKind::IntConversionFailed`] - If converting the header length to u32 fails - /// * [`PackFileErrorKind::WritingBinaryRepresentationFailed`] - If the header could not be written - /// - /// [`PackerErrorKind::IntConversionFailed`]: crate::error::PackerErrorKind::IntConversionFailed - /// [`PackFileErrorKind::WritingBinaryRepresentationFailed`]: crate::error::PackFileErrorKind::WritingBinaryRepresentationFailed + /// * If converting the header length to u32 fails + /// * If the header could not be written fn write_header(&mut self) -> RusticResult<()> { // compute the pack header let data = PackHeaderRef::from_index_pack(&self.index) @@ -713,11 +701,8 @@ impl RawPacker { /// /// # Errors /// - /// * [`PackerErrorKind::IntConversionFailed`] - If converting the header length to u32 fails - /// * [`PackFileErrorKind::WritingBinaryRepresentationFailed`] - If the header could not be written - /// - /// [`PackerErrorKind::IntConversionFailed`]: crate::error::PackerErrorKind::IntConversionFailed - /// [`PackFileErrorKind::WritingBinaryRepresentationFailed`]: crate::error::PackFileErrorKind::WritingBinaryRepresentationFailed + /// * If converting the header length to u32 fails + /// * If the header could not be written fn save(&mut self) -> RusticResult<()> { if self.size == 0 { return Ok(()); @@ -855,7 +840,7 @@ impl Actor { /// /// # Panics /// - /// If the receiver is not present + /// * If the receiver is not present fn finalize(self) -> RusticResult<()> { // cancel channel drop(self.sender); @@ -899,7 +884,7 @@ impl Repacker { /// /// # Errors /// - /// If the Packer could not be created + /// * If the Packer could not be created pub fn new( be: BE, blob_type: BlobType, @@ -925,8 +910,8 @@ impl Repacker { /// /// # Errors /// - /// If the blob could not be added - /// If reading the blob from the backend fails + /// * If the blob could not be added + /// * If reading the blob from the backend fails pub fn add_fast(&self, pack_id: &PackId, blob: &IndexBlob) -> RusticResult<()> { let data = self.be.read_partial( FileType::Pack, @@ -962,8 +947,8 @@ impl Repacker { /// /// # Errors /// - /// If the blob could not be added - /// If reading the blob from the backend fails + /// * If the blob could not be added + /// * If reading the blob from the backend fails pub fn add(&self, pack_id: &PackId, blob: &IndexBlob) -> RusticResult<()> { let data = self.be.read_encrypted_partial( FileType::Pack, diff --git a/crates/core/src/blob/tree.rs b/crates/core/src/blob/tree.rs index dd3dc6d6..a19eb429 100644 --- a/crates/core/src/blob/tree.rs +++ b/crates/core/src/blob/tree.rs @@ -132,15 +132,12 @@ impl Tree { /// /// # Errors /// - /// * [`TreeErrorKind::BlobIdNotFound`] - If the tree ID is not found in the backend. - /// * [`TreeErrorKind::DeserializingTreeFailed`] - If deserialization fails. + /// * If the tree ID is not found in the backend. + /// * If deserialization fails. /// /// # Returns /// /// The deserialized tree. - /// - /// [`TreeErrorKind::BlobIdNotFound`]: crate::error::TreeErrorKind::BlobIdNotFound - /// [`TreeErrorKind::DeserializingTreeFailed`]: crate::error::TreeErrorKind::DeserializingTreeFailed pub(crate) fn from_backend( be: &impl DecryptReadBackend, index: &impl ReadGlobalIndex, @@ -175,13 +172,9 @@ impl Tree { /// /// # Errors /// - /// * [`TreeErrorKind::NotADirectory`] - If the path is not a directory. - /// * [`TreeErrorKind::PathNotFound`] - If the path is not found. - /// * [`TreeErrorKind::PathIsNotUtf8Conform`] - If the path is not UTF-8 conform. - /// - /// [`TreeErrorKind::NotADirectory`]: crate::error::TreeErrorKind::NotADirectory - /// [`TreeErrorKind::PathNotFound`]: crate::error::TreeErrorKind::PathNotFound - /// [`TreeErrorKind::PathIsNotUtf8Conform`]: crate::error::TreeErrorKind::PathIsNotUtf8Conform + /// * If the path is not a directory. + /// * If the path is not found. + /// * If the path is not UTF-8 conform. pub(crate) fn node_from_path( be: &impl DecryptReadBackend, index: &impl ReadGlobalIndex, @@ -442,11 +435,8 @@ pub struct FindMatches { /// /// # Errors /// -/// * [`TreeErrorKind::ContainsCurrentOrParentDirectory`] - If the component is a current or parent directory. -/// * [`TreeErrorKind::PathIsNotUtf8Conform`] - If the component is not UTF-8 conform. -/// -/// [`TreeErrorKind::ContainsCurrentOrParentDirectory`]: crate::error::TreeErrorKind::ContainsCurrentOrParentDirectory -/// [`TreeErrorKind::PathIsNotUtf8Conform`]: crate::error::TreeErrorKind::PathIsNotUtf8Conform +/// * If the component is a current or parent directory. +/// * If the component is not UTF-8 conform. pub(crate) fn comp_to_osstr(p: Component<'_>) -> TreeResult> { let s = match p { Component::RootDir => None, @@ -548,11 +538,8 @@ where /// /// # Errors /// - /// * [`TreeErrorKind::BlobIdNotFound`] - If the tree ID is not found in the backend. - /// * [`TreeErrorKind::DeserializingTreeFailed`] - If deserialization fails. - /// - /// [`TreeErrorKind::BlobIdNotFound`]: crate::error::TreeErrorKind::BlobIdNotFound - /// [`TreeErrorKind::DeserializingTreeFailed`]: crate::error::TreeErrorKind::DeserializingTreeFailed + /// * If the tree ID is not found in the backend. + /// * If deserialization fails. pub fn new(be: BE, index: &'a I, node: &Node) -> RusticResult { Self::new_streamer(be, index, node, None, true) } @@ -568,11 +555,8 @@ where /// /// # Errors /// - /// * [`TreeErrorKind::BlobIdNotFound`] - If the tree ID is not found in the backend. - /// * [`TreeErrorKind::DeserializingTreeFailed`] - If deserialization fails. - /// - /// [`TreeErrorKind::BlobIdNotFound`]: crate::error::TreeErrorKind::BlobIdNotFound - /// [`TreeErrorKind::DeserializingTreeFailed`]: crate::error::TreeErrorKind::DeserializingTreeFailed + /// * If the tree ID is not found in the backend. + /// * If deserialization fails. fn new_streamer( be: BE, index: &'a I, @@ -608,11 +592,8 @@ where /// /// # Errors /// - /// * [`TreeErrorKind::BuildingNodeStreamerFailed`] - If building the streamer fails. - /// * [`TreeErrorKind::ReadingFileStringFromGlobsFailed`] - If reading a glob file fails. - /// - /// [`TreeErrorKind::BuildingNodeStreamerFailed`]: crate::error::TreeErrorKind::BuildingNodeStreamerFailed - /// [`TreeErrorKind::ReadingFileStringFromGlobsFailed`]: crate::error::TreeErrorKind::ReadingFileStringFromGlobsFailed + /// * If building the streamer fails. + /// * If reading a glob file fails. pub fn new_with_glob( be: BE, index: &'a I, @@ -805,9 +786,7 @@ impl TreeStreamerOnce

{ /// /// # Errors /// - /// * [`TreeErrorKind::SendingCrossbeamMessageFailed`] - If sending the message fails. - /// - /// [`TreeErrorKind::SendingCrossbeamMessageFailed`]: crate::error::TreeErrorKind::SendingCrossbeamMessageFailed + /// * If sending the message fails. pub fn new( be: &BE, index: &I, @@ -878,9 +857,7 @@ impl TreeStreamerOnce

{ /// /// # Errors /// - /// * [`TreeErrorKind::SendingCrossbeamMessageFailed`] - If sending the message fails. - /// - /// [`TreeErrorKind::SendingCrossbeamMessageFailed`]: crate::error::TreeErrorKind::SendingCrossbeamMessageFailed + /// * If sending the message fails. fn add_pending(&mut self, path: PathBuf, id: TreeId, count: usize) -> TreeResult { if self.visited.insert(id) { self.queue_in diff --git a/crates/core/src/commands/backup.rs b/crates/core/src/commands/backup.rs index 20da508a..60969c3e 100644 --- a/crates/core/src/commands/backup.rs +++ b/crates/core/src/commands/backup.rs @@ -196,21 +196,15 @@ pub struct BackupOptions { /// /// # Errors /// -/// * [`PackerErrorKind::SendingCrossbeamMessageFailed`] - If sending the message to the raw packer fails. -/// * [`PackerErrorKind::IntConversionFailed`] - If converting the data length to u64 fails -/// * [`PackerErrorKind::SendingCrossbeamMessageFailed`] - If sending the message to the raw packer fails. -/// * [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`] - If the index file could not be serialized. -/// * [`SnapshotFileErrorKind::OutOfRange`] - If the time is not in the range of `Local::now()` +/// * If sending the message to the raw packer fails. +/// * If converting the data length to u64 fails +/// * If sending the message to the raw packer fails. +/// * If the index file could not be serialized. +/// * If the time is not in the range of `Local::now()` /// /// # Returns /// /// The snapshot pointing to the backup'ed data. -/// -/// [`PackerErrorKind::SendingCrossbeamMessageFailed`]: crate::error::PackerErrorKind::SendingCrossbeamMessageFailed -/// [`PackerErrorKind::IntConversionFailed`]: crate::error::PackerErrorKind::IntConversionFailed -/// [`PackerErrorKind::SendingCrossbeamMessageFailed`]: crate::error::PackerErrorKind::SendingCrossbeamMessageFailed -/// [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`]: crate::error::CryptBackendErrorKind::SerializingToJsonByteVectorFailed -/// [`SnapshotFileErrorKind::OutOfRange`]: crate::error::SnapshotFileErrorKind::OutOfRange pub(crate) fn backup( repo: &Repository, opts: &BackupOptions, diff --git a/crates/core/src/commands/cat.rs b/crates/core/src/commands/cat.rs index 05453d3f..8d355ea8 100644 --- a/crates/core/src/commands/cat.rs +++ b/crates/core/src/commands/cat.rs @@ -27,17 +27,13 @@ use crate::{ /// /// # Errors /// -/// * [`IdErrorKind::HexError`] - If the string is not a valid hexadecimal string -/// * [`BackendAccessErrorKind::NoSuitableIdFound`] - If no id could be found. -/// * [`BackendAccessErrorKind::IdNotUnique`] - If the id is not unique. +/// * If the string is not a valid hexadecimal string +/// * If no id could be found. +/// * If the id is not unique. /// /// # Returns /// /// The data read. -/// -/// [`IdErrorKind::HexError`]: crate::error::IdErrorKind::HexError -/// [`BackendAccessErrorKind::NoSuitableIdFound`]: crate::error::BackendAccessErrorKind::NoSuitableIdFound -/// [`BackendAccessErrorKind::IdNotUnique`]: crate::error::BackendAccessErrorKind::IdNotUnique pub(crate) fn cat_file( repo: &Repository, tpe: FileType, @@ -63,9 +59,7 @@ pub(crate) fn cat_file( /// /// # Errors /// -/// * [`IdErrorKind::HexError`] - If the string is not a valid hexadecimal string -/// -/// [`IdErrorKind::HexError`]: crate::error::IdErrorKind::HexError +/// * If the string is not a valid hexadecimal string pub(crate) fn cat_blob( repo: &Repository, tpe: BlobType, @@ -92,13 +86,11 @@ pub(crate) fn cat_blob( /// /// # Errors /// -/// * [`CommandErrorKind::PathIsNoDir`] - If the path is not a directory. +/// * If the path is not a directory. /// /// # Returns /// /// The data read. -/// -/// [`CommandErrorKind::PathIsNoDir`]: crate::error::CommandErrorKind::PathIsNoDir pub(crate) fn cat_tree( repo: &Repository, snap: &str, diff --git a/crates/core/src/commands/check.rs b/crates/core/src/commands/check.rs index 95c99e75..e52338d9 100644 --- a/crates/core/src/commands/check.rs +++ b/crates/core/src/commands/check.rs @@ -325,7 +325,7 @@ pub struct CheckOptions { /// /// # Errors /// -/// If the repository is corrupted +/// * If the repository is corrupted /// /// # Panics /// @@ -427,7 +427,7 @@ pub(crate) fn check_repository( /// /// # Errors /// -/// If a file is missing or has a different size +/// * If a file is missing or has a different size fn check_hot_files( be: &impl ReadBackend, be_hot: &impl ReadBackend, @@ -473,7 +473,7 @@ fn check_hot_files( /// /// # Errors /// -/// If a file is missing or has a different size +/// * If a file is missing or has a different size fn check_cache_files( _concurrency: usize, cache: &Cache, @@ -530,7 +530,7 @@ fn check_cache_files( /// /// # Errors /// -/// If a pack is missing or has a different size +/// * If a pack is missing or has a different size /// /// # Returns /// @@ -610,7 +610,7 @@ fn check_packs( /// /// # Errors /// -/// If a pack is missing or has a different size +/// * If a pack is missing or has a different size fn check_packs_list(be: &impl ReadBackend, mut packs: HashMap) -> RusticResult<()> { for (id, size) in be.list_with_size(FileType::Pack)? { match packs.remove(&PackId::from(id)) { @@ -637,7 +637,7 @@ fn check_packs_list(be: &impl ReadBackend, mut packs: HashMap) -> R /// /// # Errors /// -/// If a pack is missing or has a different size +/// * If a pack is missing or has a different size fn check_packs_list_hot( be: &impl ReadBackend, mut treepacks: HashMap, @@ -674,7 +674,7 @@ fn check_packs_list_hot( /// /// # Errors /// -/// If a snapshot or tree is missing or has a different size +/// * If a snapshot or tree is missing or has a different size fn check_trees( be: &impl DecryptReadBackend, index: &impl ReadGlobalIndex, @@ -756,11 +756,11 @@ fn check_trees( /// /// # Errors /// -/// If the pack is invalid +/// * If the pack is invalid /// /// # Panics /// -/// If zstd decompression fails. +/// * If zstd decompression fails. fn check_pack( be: &impl DecryptReadBackend, index_pack: IndexPack, diff --git a/crates/core/src/commands/dump.rs b/crates/core/src/commands/dump.rs index 0aa75cac..e235d827 100644 --- a/crates/core/src/commands/dump.rs +++ b/crates/core/src/commands/dump.rs @@ -22,9 +22,7 @@ use crate::{ /// /// # Errors /// -/// * [`CommandErrorKind::DumpNotSupported`] - If the node is not a file. -/// -/// [`CommandErrorKind::DumpNotSupported`]: crate::error::CommandErrorKind::DumpNotSupported +/// * If the node is not a file. pub(crate) fn dump( repo: &Repository, node: &Node, diff --git a/crates/core/src/commands/forget.rs b/crates/core/src/commands/forget.rs index 299ad7bc..744ea3a5 100644 --- a/crates/core/src/commands/forget.rs +++ b/crates/core/src/commands/forget.rs @@ -72,7 +72,7 @@ impl ForgetGroups { /// /// # Errors /// -/// If keep options are not valid +/// * If keep options are not valid /// /// # Returns /// @@ -511,7 +511,7 @@ impl KeepOptions { /// /// # Errors /// - /// If keep options are not valid + /// * If keep options are not valid /// /// # Returns /// diff --git a/crates/core/src/commands/init.rs b/crates/core/src/commands/init.rs index 6047acb8..46037348 100644 --- a/crates/core/src/commands/init.rs +++ b/crates/core/src/commands/init.rs @@ -32,13 +32,11 @@ use crate::{ /// /// # Errors /// -/// * [`PolynomialErrorKind::NoSuitablePolynomialFound`] - If no polynomial could be found in one million tries. +/// * If no polynomial could be found in one million tries. /// /// # Returns /// /// A tuple of the key and the config file. -/// -/// [`PolynomialErrorKind::NoSuitablePolynomialFound`]: crate::error::PolynomialErrorKind::NoSuitablePolynomialFound pub(crate) fn init( repo: &Repository, pass: &str, diff --git a/crates/core/src/commands/key.rs b/crates/core/src/commands/key.rs index dfd54508..a057f1f5 100644 --- a/crates/core/src/commands/key.rs +++ b/crates/core/src/commands/key.rs @@ -43,13 +43,11 @@ pub struct KeyOptions { /// /// # Errors /// -/// * [`CommandErrorKind::FromJsonError`] - If the key could not be serialized +/// * If the key could not be serialized /// /// # Returns /// /// The id of the key. -/// -/// [`CommandErrorKind::FromJsonError`]: crate::error::CommandErrorKind::FromJsonError pub(crate) fn add_current_key_to_repo( repo: &Repository, opts: &KeyOptions, @@ -96,13 +94,11 @@ pub(crate) fn init_key( /// /// # Errors /// -/// * [`CommandErrorKind::FromJsonError`] - If the key could not be serialized. +/// * If the key could not be serialized. /// /// # Returns /// /// The id of the key. -/// -/// [`CommandErrorKind::FromJsonError`]: crate::error::CommandErrorKind::FromJsonError pub(crate) fn add_key_to_repo( repo: &Repository, opts: &KeyOptions, diff --git a/crates/core/src/commands/merge.rs b/crates/core/src/commands/merge.rs index a50766ef..7e239628 100644 --- a/crates/core/src/commands/merge.rs +++ b/crates/core/src/commands/merge.rs @@ -86,13 +86,11 @@ pub(crate) fn merge_snapshots( /// /// # Errors /// -/// * [`CommandErrorKind::ConversionToU64Failed`] - If the size of the tree is too large +/// * If the size of the tree is too large /// /// # Returns /// /// The merged tree -/// -/// [`CommandErrorKind::ConversionToU64Failed`]: crate::error::CommandErrorKind::ConversionToU64Failed pub(crate) fn merge_trees( repo: &Repository, trees: &[TreeId], diff --git a/crates/core/src/commands/prune.rs b/crates/core/src/commands/prune.rs index c15d2a46..417c222c 100644 --- a/crates/core/src/commands/prune.rs +++ b/crates/core/src/commands/prune.rs @@ -90,7 +90,7 @@ pub struct PruneOptions { /// /// # Warning /// - /// Only use if you are sure the repository is not accessed by parallel processes! + /// * Only use if you are sure the repository is not accessed by parallel processes! #[cfg_attr(feature = "clap", clap(long))] pub instant_delete: bool, @@ -98,7 +98,7 @@ pub struct PruneOptions { /// /// # Warning /// - /// If prune aborts, this can lead to a (partly) missing index which must be repaired! + /// * If prune aborts, this can lead to a (partly) missing index which must be repaired! #[cfg_attr(feature = "clap", clap(long))] pub early_delete_index: bool, @@ -128,10 +128,9 @@ pub struct PruneOptions { /// /// # Warning /// - /// Use this option with care! - /// - /// If you specify snapshots which are not deleted, running the resulting `PrunePlan` - /// will remove data which is used within those snapshots! + /// * Use this option with care! + /// * If you specify snapshots which are not deleted, running the resulting `PrunePlan` + /// will remove data which is used within those snapshots! pub ignore_snaps: Vec, } @@ -168,11 +167,8 @@ impl PruneOptions { /// /// # Errors /// - /// * [`CommandErrorKind::RepackUncompressedRepoV1`] - If `repack_uncompressed` is set and the repository is a version 1 repository - /// * [`CommandErrorKind::FromOutOfRangeError`] - If `keep_pack` or `keep_delete` is out of range - /// - /// [`CommandErrorKind::RepackUncompressedRepoV1`]: crate::error::CommandErrorKind::RepackUncompressedRepoV1 - /// [`CommandErrorKind::FromOutOfRangeError`]: crate::error::CommandErrorKind::FromOutOfRangeError + /// * If `repack_uncompressed` is set and the repository is a version 1 repository + /// * If `keep_pack` or `keep_delete` is out of range #[deprecated( since = "0.5.2", note = "Use `PrunePlan::from_prune_options()` instead" @@ -691,9 +687,7 @@ impl PrunePlan { /// # Errors /// /// * If `repack_uncompressed` is set and the repository is a version 1 repository - /// * [`CommandErrorKind::FromOutOfRangeError`] - If `keep_pack` or `keep_delete` is out of range - /// - /// [`CommandErrorKind::FromOutOfRangeError`]: crate::error::CommandErrorKind::FromOutOfRangeError + /// * If `keep_pack` or `keep_delete` is out of range pub fn from_prune_options( repo: &Repository, opts: &PruneOptions, @@ -810,9 +804,7 @@ impl PrunePlan { /// /// # Errors /// - /// * [`CommandErrorKind::BlobsMissing`] - If a blob is missing - /// - /// [`CommandErrorKind::BlobsMissing`]: crate::error::CommandErrorKind::BlobsMissing + /// * If a blob is missing fn check(&self) -> RusticResult<()> { for (id, count) in &self.used_ids { if *count == 0 { @@ -1074,13 +1066,9 @@ impl PrunePlan { /// /// # Errors /// - /// * [`CommandErrorKind::NoDecision`] - If a pack is undecided - /// * [`CommandErrorKind::PackSizeNotMatching`] - If the size of a pack does not match - /// * [`CommandErrorKind::PackNotExisting`] - If a pack does not exist - /// - /// [`CommandErrorKind::NoDecision`]: crate::error::CommandErrorKind::NoDecision - /// [`CommandErrorKind::PackSizeNotMatching`]: crate::error::CommandErrorKind::PackSizeNotMatching - /// [`CommandErrorKind::PackNotExisting`]: crate::error::CommandErrorKind::PackNotExisting + /// * If a pack is undecided + /// * If the size of a pack does not match + /// * If a pack does not exist fn check_existing_packs(&mut self) -> RusticResult<()> { for pack in self.index_files.iter().flat_map(|index| &index.packs) { let existing_size = self.existing_packs.remove(&pack.id); @@ -1191,8 +1179,8 @@ impl PrunePlan { /// /// # Errors /// - /// * [`CommandErrorKind::NotAllowedWithAppendOnly`] - If the repository is in append-only mode - /// * [`CommandErrorKind::NoDecision`] - If a pack has no decision + /// * If the repository is in append-only mode + /// * If a pack has no decision /// /// # Returns /// @@ -1223,8 +1211,8 @@ impl PrunePlan { /// /// # Errors /// -/// * [`CommandErrorKind::NotAllowedWithAppendOnly`] - If the repository is in append-only mode -/// * [`CommandErrorKind::NoDecision`] - If a pack has no decision +/// * If the repository is in append-only mode +/// * If a pack has no decision /// /// # Returns /// diff --git a/crates/core/src/commands/repair/snapshots.rs b/crates/core/src/commands/repair/snapshots.rs index 4514f764..154e18b4 100644 --- a/crates/core/src/commands/repair/snapshots.rs +++ b/crates/core/src/commands/repair/snapshots.rs @@ -31,7 +31,7 @@ pub struct RepairSnapshotsOptions { /// /// # Warning /// - /// This can result in data loss! + /// * This can result in data loss! #[cfg_attr(feature = "clap", clap(long))] pub delete: bool, diff --git a/crates/core/src/commands/repoinfo.rs b/crates/core/src/commands/repoinfo.rs index 7aa8c95f..6e5af8cf 100644 --- a/crates/core/src/commands/repoinfo.rs +++ b/crates/core/src/commands/repoinfo.rs @@ -187,7 +187,7 @@ pub struct RepoFileInfo { /// /// # Errors /// -/// If files could not be listed. +/// * If files could not be listed. pub(crate) fn collect_file_info(be: &impl ReadBackend) -> RusticResult> { let mut files = Vec::with_capacity(ALL_FILE_TYPES.len()); for tpe in ALL_FILE_TYPES { diff --git a/crates/core/src/commands/restore.rs b/crates/core/src/commands/restore.rs index 9129f975..c647d75a 100644 --- a/crates/core/src/commands/restore.rs +++ b/crates/core/src/commands/restore.rs @@ -51,7 +51,7 @@ pub struct RestoreOptions { /// /// # Warning /// - /// Use with care, maybe first try this with --dry-run? + /// * Use with care, maybe first try this with `--dry-run`? #[cfg_attr(feature = "clap", clap(long))] pub delete: bool, @@ -111,7 +111,7 @@ pub struct RestoreStats { /// /// # Errors /// -/// If the restore failed. +/// * If the restore failed. pub(crate) fn restore_repository( file_infos: RestorePlan, repo: &Repository, @@ -145,11 +145,8 @@ pub(crate) fn restore_repository( /// /// # Errors /// -/// * [`CommandErrorKind::ErrorCreating`] - If a directory could not be created. -/// * [`CommandErrorKind::ErrorCollecting`] - If the restore information could not be collected. -/// -/// [`CommandErrorKind::ErrorCreating`]: crate::error::CommandErrorKind::ErrorCreating -/// [`CommandErrorKind::ErrorCollecting`]: crate::error::CommandErrorKind::ErrorCollecting +/// * If a directory could not be created. +/// * If the restore information could not be collected. #[allow(clippy::too_many_lines)] pub(crate) fn collect_and_prepare( repo: &Repository, @@ -338,7 +335,7 @@ pub(crate) fn collect_and_prepare( /// /// # Errors /// -/// If the restore failed. +/// * If the restore failed. fn restore_metadata( mut node_streamer: impl Iterator>, opts: RestoreOptions, @@ -426,11 +423,8 @@ pub(crate) fn set_metadata( /// /// # Errors /// -/// * [`CommandErrorKind::ErrorSettingLength`] - If the length of a file could not be set. -/// * [`CommandErrorKind::FromRayonError`] - If the restore failed. -/// -/// [`CommandErrorKind::ErrorSettingLength`]: crate::error::CommandErrorKind::ErrorSettingLength -/// [`CommandErrorKind::FromRayonError`]: crate::error::CommandErrorKind::FromRayonError +/// * If the length of a file could not be set. +/// * If the restore failed. #[allow(clippy::too_many_lines)] fn restore_contents( repo: &Repository, @@ -659,7 +653,7 @@ impl RestorePlan { /// /// # Errors /// - /// If the file could not be added. + /// * If the file could not be added. fn add_file( &mut self, dest: &LocalDestination, diff --git a/crates/core/src/crypto/aespoly1305.rs b/crates/core/src/crypto/aespoly1305.rs index cebdb2f3..4955f721 100644 --- a/crates/core/src/crypto/aespoly1305.rs +++ b/crates/core/src/crypto/aespoly1305.rs @@ -84,7 +84,7 @@ impl CryptoKey for Key { /// /// # Errors /// - /// If the MAC couldn't be checked. + /// * If the MAC couldn't be checked. fn decrypt_data(&self, data: &[u8]) -> RusticResult> { if data.len() < 16 { return Err(RusticError::new( @@ -115,7 +115,7 @@ impl CryptoKey for Key { /// /// # Errors /// - /// If the data could not be encrypted. + /// * If the data could not be encrypted. fn encrypt_data(&self, data: &[u8]) -> RusticResult> { let mut nonce = Nonce::default(); thread_rng().fill_bytes(&mut nonce); diff --git a/crates/core/src/id.rs b/crates/core/src/id.rs index 32d5b549..1abc3d89 100644 --- a/crates/core/src/id.rs +++ b/crates/core/src/id.rs @@ -107,19 +107,15 @@ impl Id { /// /// # Errors /// - /// * [`IdErrorKind::HexError`] - If the string is not a valid hexadecimal string + /// * If the string is not a valid hexadecimal string /// /// # Examples /// /// ``` - /// use rustic_core::Id; - /// + /// # use rustic_core::Id; /// let id = Id::from_hex("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef").unwrap(); - /// - /// assert_eq!(id.to_hex().as_str(), "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"); + /// # assert_eq!(id.to_hex().as_str(), "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"); /// ``` - /// - /// [`IdErrorKind::HexError`]: crate::error::IdErrorKind::HexError #[deprecated(note = "use FromStr::from_str instead")] pub fn from_hex(s: &str) -> RusticResult { s.parse() @@ -153,7 +149,7 @@ impl Id { /// /// # Panics /// - /// Panics if the `hex` crate fails to encode the hash + /// * Panics if the `hex` crate fails to encode the hash // TODO! - remove the panic #[must_use] pub fn to_hex(self) -> HexId { @@ -221,7 +217,7 @@ impl HexId { /// /// # Panics /// - /// If the [`HexId`] is not a valid UTF-8 string + /// * If the [`HexId`] is not a valid UTF-8 string #[must_use] pub fn as_str(&self) -> &str { // This is only ever filled with hex chars, which are ascii diff --git a/crates/core/src/index.rs b/crates/core/src/index.rs index 9f31a6ac..505216b7 100644 --- a/crates/core/src/index.rs +++ b/crates/core/src/index.rs @@ -192,9 +192,7 @@ pub trait ReadIndex { /// /// # Errors /// - /// * [`IndexErrorKind::BlobInIndexNotFound`] - If the blob could not be found in the index - /// - /// [`IndexErrorKind::BlobInIndexNotFound`]: crate::error::IndexErrorKind::BlobInIndexNotFound + /// * If the blob could not be found in the index fn blob_from_backend( &self, be: &impl DecryptReadBackend, @@ -290,7 +288,7 @@ impl GlobalIndex { /// /// # Errors /// - /// If the index could not be read + /// * If the index could not be read fn new_from_collector( be: &impl DecryptReadBackend, p: &impl Progress, @@ -325,7 +323,7 @@ impl GlobalIndex { /// /// # Errors /// - /// If the index could not be read + /// * If the index could not be read pub fn only_full_trees(be: &impl DecryptReadBackend, p: &impl Progress) -> RusticResult { Self::new_from_collector(be, p, IndexCollector::new(IndexType::DataIds)) } diff --git a/crates/core/src/index/indexer.rs b/crates/core/src/index/indexer.rs index 902f634b..8f8f143e 100644 --- a/crates/core/src/index/indexer.rs +++ b/crates/core/src/index/indexer.rs @@ -101,9 +101,7 @@ impl Indexer { /// /// # Errors /// - /// * [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`] - If the index file could not be serialized. - /// - /// [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`]: crate::error::CryptBackendErrorKind::SerializingToJsonByteVectorFailed + /// * If the index file could not be serialized. pub fn finalize(&self) -> RusticResult<()> { self.save() } @@ -112,9 +110,7 @@ impl Indexer { /// /// # Errors /// - /// * [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`] - If the index file could not be serialized. - /// - /// [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`]: crate::error::CryptBackendErrorKind::SerializingToJsonByteVectorFailed + /// * If the index file could not be serialized. pub fn save(&self) -> RusticResult<()> { if (self.file.packs.len() + self.file.packs_to_delete.len()) > 0 { _ = self.be.save_file(&self.file)?; @@ -130,9 +126,7 @@ impl Indexer { /// /// # Errors /// - /// * [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`] - If the index file could not be serialized. - /// - /// [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`]: crate::error::CryptBackendErrorKind::SerializingToJsonByteVectorFailed + /// * If the index file could not be serialized. pub fn add(&mut self, pack: IndexPack) -> RusticResult<()> { self.add_with(pack, false) } @@ -145,9 +139,7 @@ impl Indexer { /// /// # Errors /// - /// * [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`] - If the index file could not be serialized. - /// - /// [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`]: crate::error::CryptBackendErrorKind::SerializingToJsonByteVectorFailed + /// * If the index file could not be serialized. pub fn add_remove(&mut self, pack: IndexPack) -> RusticResult<()> { self.add_with(pack, true) } @@ -161,9 +153,7 @@ impl Indexer { /// /// # Errors /// - /// * [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`] - If the index file could not be serialized. - /// - /// [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`]: crate::error::CryptBackendErrorKind::SerializingToJsonByteVectorFailed + /// * If the index file could not be serialized. pub fn add_with(&mut self, pack: IndexPack, delete: bool) -> RusticResult<()> { self.count += pack.blobs.len(); diff --git a/crates/core/src/repofile/configfile.rs b/crates/core/src/repofile/configfile.rs index 0e46415d..6e86153c 100644 --- a/crates/core/src/repofile/configfile.rs +++ b/crates/core/src/repofile/configfile.rs @@ -157,9 +157,7 @@ impl ConfigFile { /// /// # Errors /// - /// * [`ConfigFileErrorKind::ParsingFailedForPolynomial`] - If the polynomial could not be parsed - /// - /// [`ConfigFileErrorKind::ParsingFailedForPolynomial`]: ConfigFileErrorKind::ParsingFailedForPolynomial + /// * If the polynomial could not be parsed pub fn poly(&self) -> RusticResult { let chunker_poly = u64::from_str_radix(&self.chunker_polynomial, 16) .map_err(|err| RusticError::with_source( @@ -176,9 +174,7 @@ impl ConfigFile { /// /// # Errors /// - /// * [`ConfigFileErrorKind::ConfigVersionNotSupported`] - If the version is not supported - /// - /// [`ConfigFileErrorKind::ConfigVersionNotSupported`]: ConfigFileErrorKind::ConfigVersionNotSupported + /// * If the version is not supported pub fn zstd(&self) -> RusticResult> { match (self.version, self.compression) { (1, _) | (2, Some(0)) => Ok(None), diff --git a/crates/core/src/repofile/keyfile.rs b/crates/core/src/repofile/keyfile.rs index ab839234..93743a20 100644 --- a/crates/core/src/repofile/keyfile.rs +++ b/crates/core/src/repofile/keyfile.rs @@ -103,15 +103,12 @@ impl KeyFile { /// /// # Errors /// - /// * [`KeyFileErrorKind::InvalidSCryptParameters`] - If the parameters of the key derivation function are invalid - /// * [`KeyFileErrorKind::OutputLengthInvalid`] - If the output length of the key derivation function is invalid + /// * If the parameters of the key derivation function are invalid + /// * If the output length of the key derivation function is invalid /// /// # Returns /// /// The generated key - /// - /// [`KeyFileErrorKind::InvalidSCryptParameters`]: crate::error::KeyFileErrorKind::InvalidSCryptParameters - /// [`KeyFileErrorKind::OutputLengthInvalid`]: crate::error::KeyFileErrorKind::OutputLengthInvalid pub fn kdf_key(&self, passwd: &impl AsRef<[u8]>) -> RusticResult { let params = Params::new( log_2(self.n).map_err(|err| { @@ -154,13 +151,11 @@ impl KeyFile { /// /// # Errors /// - /// * [`KeyFileErrorKind::DeserializingFromSliceFailed`] - If the data could not be deserialized + /// * If the data could not be deserialized /// /// # Returns /// /// The extracted key - /// - /// [`KeyFileErrorKind::DeserializingFromSliceFailed`]: crate::error::KeyFileErrorKind::DeserializingFromSliceFailed pub fn key_from_data(&self, key: &Key) -> RusticResult { let dec_data = key.decrypt_data(&self.data)?; @@ -186,13 +181,11 @@ impl KeyFile { /// /// # Errors /// - /// * [`KeyFileErrorKind::InvalidSCryptParameters`] - If the parameters of the key derivation function are invalid + /// * If the parameters of the key derivation function are invalid /// /// # Returns /// /// The extracted key - /// - /// [`KeyFileErrorKind::InvalidSCryptParameters`]: crate::error::KeyFileErrorKind::InvalidSCryptParameters pub fn key_from_password(&self, passwd: &impl AsRef<[u8]>) -> RusticResult { self.key_from_data(&self.kdf_key(passwd)?) } @@ -209,15 +202,12 @@ impl KeyFile { /// /// # Errors /// - /// * [`KeyFileErrorKind::OutputLengthInvalid`] - If the output length of the key derivation function is invalid - /// * [`KeyFileErrorKind::CouldNotSerializeAsJsonByteVector`] - If the [`KeyFile`] could not be serialized + /// * If the output length of the key derivation function is invalid + /// * If the [`KeyFile`] could not be serialized /// /// # Returns /// /// The generated [`KeyFile`] - /// - /// [`KeyFileErrorKind::OutputLengthInvalid`]: crate::error::KeyFileErrorKind::OutputLengthInvalid - /// [`KeyFileErrorKind::CouldNotSerializeAsJsonByteVector`]: crate::error::KeyFileErrorKind::CouldNotSerializeAsJsonByteVector pub fn generate( key: Key, passwd: &impl AsRef<[u8]>, @@ -300,13 +290,11 @@ impl KeyFile { /// /// # Errors /// -/// * [`KeyFileErrorKind::ConversionFromU32ToU8Failed`] - If the conversion from `u32` to `u8` failed +/// * If the conversion from `u32` to `u8` failed /// /// # Returns /// /// The logarithm to base 2 of the given number -/// -/// [`KeyFileErrorKind::ConversionFromU32ToU8Failed`]: crate::error::KeyFileErrorKind::ConversionFromU32ToU8Failed fn log_2(x: u32) -> KeyFileResult { assert!(x > 0); Ok(u8::try_from(constants::num_bits::()).map_err(|err| { @@ -407,13 +395,11 @@ pub(crate) fn key_from_backend( /// /// # Errors /// -/// * [`KeyFileErrorKind::NoSuitableKeyFound`] - If no suitable key was found +/// * If no suitable key was found /// /// # Returns /// /// The found key -/// -/// [`KeyFileErrorKind::NoSuitableKeyFound`]: crate::error::KeyFileErrorKind::NoSuitableKeyFound pub(crate) fn find_key_in_backend( be: &B, passwd: &impl AsRef<[u8]>, diff --git a/crates/core/src/repofile/packfile.rs b/crates/core/src/repofile/packfile.rs index cc594546..64564335 100644 --- a/crates/core/src/repofile/packfile.rs +++ b/crates/core/src/repofile/packfile.rs @@ -73,9 +73,7 @@ impl PackHeaderLength { /// /// # Errors /// - /// * [`PackFileErrorKind::ReadingBinaryRepresentationFailed`] - If reading the binary representation failed - /// - /// [`PackFileErrorKind::ReadingBinaryRepresentationFailed`]: crate::error::PackFileErrorKind::ReadingBinaryRepresentationFailed + /// * If reading the binary representation failed pub(crate) fn from_binary(data: &[u8]) -> PackFileResult { let mut reader = Cursor::new(data); Self::read(&mut reader).map_err(PackFileErrorKind::ReadingBinaryRepresentationFailed) @@ -85,9 +83,7 @@ impl PackHeaderLength { /// /// # Errors /// - /// * [`PackFileErrorKind::WritingBinaryRepresentationFailed`] - If writing the binary representation failed - /// - /// [`PackFileErrorKind::WritingBinaryRepresentationFailed`]: crate::error::PackFileErrorKind::WritingBinaryRepresentationFailed + /// * If writing the binary representation failed pub(crate) fn to_binary(self) -> PackFileResult> { let mut writer = Cursor::new(Vec::with_capacity(4)); self.write(&mut writer) @@ -236,9 +232,7 @@ impl PackHeader { /// /// # Errors /// - /// * [`PackFileErrorKind::ReadingBinaryRepresentationFailed`] - If reading the binary representation failed - /// - /// [`PackFileErrorKind::ReadingBinaryRepresentationFailed`]: crate::error::PackFileErrorKind::ReadingBinaryRepresentationFailed + /// * If reading the binary representation failed pub(crate) fn from_binary(pack: &[u8]) -> PackFileResult { let mut reader = Cursor::new(pack); let mut offset = 0; @@ -266,15 +260,10 @@ impl PackHeader { /// /// # Errors /// - /// * [`PackFileErrorKind::ReadingBinaryRepresentationFailed`] - If reading the binary representation failed - /// * [`PackFileErrorKind::HeaderLengthTooLarge`] - If the header length is too large - /// * [`PackFileErrorKind::HeaderLengthDoesNotMatchHeaderContents`] - If the header length does not match the header contents - /// * [`PackFileErrorKind::HeaderPackSizeComputedDoesNotMatchRealPackFile`] - If the pack size computed from the header does not match the real pack file size - /// - /// [`PackFileErrorKind::ReadingBinaryRepresentationFailed`]: crate::error::PackFileErrorKind::ReadingBinaryRepresentationFailed - /// [`PackFileErrorKind::HeaderLengthTooLarge`]: crate::error::PackFileErrorKind::HeaderLengthTooLarge - /// [`PackFileErrorKind::HeaderLengthDoesNotMatchHeaderContents`]: crate::error::PackFileErrorKind::HeaderLengthDoesNotMatchHeaderContents - /// [`PackFileErrorKind::HeaderPackSizeComputedDoesNotMatchRealPackFile`]: crate::error::PackFileErrorKind::HeaderPackSizeComputedDoesNotMatchRealPackFile + /// * If reading the binary representation failed + /// * If the header length is too large + /// * If the header length does not match the header contents + /// * If the pack size computed from the header does not match the real pack file size pub(crate) fn from_file( be: &impl DecryptReadBackend, id: PackId, @@ -402,9 +391,7 @@ impl<'a> PackHeaderRef<'a> { /// /// # Errors /// - /// * [`PackFileErrorKind::WritingBinaryRepresentationFailed`] - If writing the binary representation failed - /// - /// [`PackFileErrorKind::WritingBinaryRepresentationFailed`]: crate::error::PackFileErrorKind::WritingBinaryRepresentationFailed + /// * If writing the binary representation failed pub(crate) fn to_binary(&self) -> PackFileResult> { let mut writer = Cursor::new(Vec::with_capacity(self.pack_size() as usize)); // collect header entries diff --git a/crates/core/src/repofile/snapshotfile.rs b/crates/core/src/repofile/snapshotfile.rs index ccb598ec..8b779059 100644 --- a/crates/core/src/repofile/snapshotfile.rs +++ b/crates/core/src/repofile/snapshotfile.rs @@ -141,13 +141,11 @@ impl SnapshotOptions { /// /// # Errors /// - /// * [`SnapshotFileErrorKind::NonUnicodeTag`] - If the tag is not valid unicode + /// * If the tag is not valid unicode /// /// # Returns /// /// The modified [`SnapshotOptions`] - /// - /// [`SnapshotFileErrorKind::NonUnicodeTag`]: crate::error::SnapshotFileErrorKind::NonUnicodeTag pub fn add_tags(mut self, tag: &str) -> RusticResult { self.tags.push(StringList::from_str(tag).map_err(|err| { RusticError::with_source( @@ -164,13 +162,11 @@ impl SnapshotOptions { /// /// # Errors /// - /// * [`SnapshotFileErrorKind::NonUnicodeHostname`] - If the hostname is not valid unicode + /// * If the hostname is not valid unicode /// /// # Returns /// /// The new [`SnapshotFile`] - /// - /// [`SnapshotFileErrorKind::NonUnicodeHostname`]: crate::error::SnapshotFileErrorKind::NonUnicodeHostname pub fn to_snapshot(&self) -> RusticResult { SnapshotFile::from_options(self) } @@ -270,9 +266,7 @@ impl SnapshotSummary { /// /// # Errors /// - /// * [`SnapshotFileErrorKind::OutOfRange`] - If the time is not in the range of `Local::now()` - /// - /// [`SnapshotFileErrorKind::OutOfRange`]: crate::error::SnapshotFileErrorKind::OutOfRange + /// * If the time is not in the range of `Local::now()` pub(crate) fn finalize(&mut self, snap_time: DateTime) -> SnapshotFileResult<()> { let end_time = Local::now(); self.backup_duration = (end_time - self.backup_start) @@ -393,17 +387,13 @@ impl SnapshotFile { /// /// # Errors /// - /// * [`SnapshotFileErrorKind::NonUnicodeHostname`] - If the hostname is not valid unicode - /// * [`SnapshotFileErrorKind::OutOfRange`] - If the delete time is not in the range of `Local::now()` - /// * [`SnapshotFileErrorKind::ReadingDescriptionFailed`] - If the description file could not be read + /// * If the hostname is not valid unicode + /// * If the delete time is not in the range of `Local::now()` + /// * If the description file could not be read /// /// # Note /// /// This is the preferred way to create a new [`SnapshotFile`] to be used within [`crate::Repository::backup`]. - /// - /// [`SnapshotFileErrorKind::NonUnicodeHostname`]: crate::error::SnapshotFileErrorKind::NonUnicodeHostname - /// [`SnapshotFileErrorKind::OutOfRange`]: crate::error::SnapshotFileErrorKind::OutOfRange - /// [`SnapshotFileErrorKind::ReadingDescriptionFailed`]: crate::error::SnapshotFileErrorKind::ReadingDescriptionFailed pub fn from_options(opts: &SnapshotOptions) -> RusticResult { let hostname = if let Some(host) = &opts.host { host.clone() @@ -511,13 +501,9 @@ impl SnapshotFile { /// /// # Errors /// - /// * [`IdErrorKind::HexError`] - If the string is not a valid hexadecimal string - /// * [`BackendAccessErrorKind::NoSuitableIdFound`] - If no id could be found. - /// * [`BackendAccessErrorKind::IdNotUnique`] - If the id is not unique. - /// - /// [`IdErrorKind::HexError`]: crate::error::IdErrorKind::HexError - /// [`BackendAccessErrorKind::NoSuitableIdFound`]: crate::error::BackendAccessErrorKind::NoSuitableIdFound - /// [`BackendAccessErrorKind::IdNotUnique`]: crate::error::BackendAccessErrorKind::IdNotUnique + /// * If the string is not a valid hexadecimal string + /// * If no id could be found. + /// * If the id is not unique. pub(crate) fn from_str( be: &B, string: &str, @@ -540,9 +526,7 @@ impl SnapshotFile { /// /// # Errors /// - /// * [`SnapshotFileErrorKind::NoSnapshotsFound`] - If no snapshots are found - /// - /// [`SnapshotFileErrorKind::NoSnapshotsFound`]: crate::error::SnapshotFileErrorKind::NoSnapshotsFound + /// * If no snapshots are found pub(crate) fn latest( be: &B, predicate: impl FnMut(&Self) -> bool + Send + Sync, @@ -585,13 +569,10 @@ impl SnapshotFile { /// * `id` - The (part of the) id of the snapshot /// /// # Errors - /// * [`IdErrorKind::HexError`] - If the string is not a valid hexadecimal string - /// * [`BackendAccessErrorKind::NoSuitableIdFound`] - If no id could be found. - /// * [`BackendAccessErrorKind::IdNotUnique`] - If the id is not unique. /// - /// [`IdErrorKind::HexError`]: crate::error::IdErrorKind::HexError - /// [`BackendAccessErrorKind::NoSuitableIdFound`]: crate::error::BackendAccessErrorKind::NoSuitableIdFound - /// [`BackendAccessErrorKind::IdNotUnique`]: crate::error::BackendAccessErrorKind::IdNotUnique + /// * If the string is not a valid hexadecimal string + /// * If no id could be found. + /// * If the id is not unique. pub(crate) fn from_id(be: &B, id: &str) -> RusticResult { info!("getting snapshot..."); let id = be.find_id(FileType::Snapshot, id)?; @@ -608,13 +589,9 @@ impl SnapshotFile { /// /// # Errors /// - /// * [`IdErrorKind::HexError`] - If the string is not a valid hexadecimal string - /// * [`BackendAccessErrorKind::NoSuitableIdFound`] - If no id could be found. - /// * [`BackendAccessErrorKind::IdNotUnique`] - If the id is not unique. - /// - /// [`IdErrorKind::HexError`]: crate::error::IdErrorKind::HexError - /// [`BackendAccessErrorKind::NoSuitableIdFound`]: crate::error::BackendAccessErrorKind::NoSuitableIdFound - /// [`BackendAccessErrorKind::IdNotUnique`]: crate::error::BackendAccessErrorKind::IdNotUnique + /// * If the string is not a valid hexadecimal string + /// * If no id could be found. + /// * If the id is not unique. pub(crate) fn from_ids>( be: &B, ids: &[T], @@ -633,13 +610,9 @@ impl SnapshotFile { /// /// # Errors /// - /// * [`IdErrorKind::HexError`] - If the string is not a valid hexadecimal string - /// * [`BackendAccessErrorKind::NoSuitableIdFound`] - If no id could be found. - /// * [`BackendAccessErrorKind::IdNotUnique`] - If the id is not unique. - /// - /// [`IdErrorKind::HexError`]: crate::error::IdErrorKind::HexError - /// [`BackendAccessErrorKind::NoSuitableIdFound`]: crate::error::BackendAccessErrorKind::NoSuitableIdFound - /// [`BackendAccessErrorKind::IdNotUnique`]: crate::error::BackendAccessErrorKind::IdNotUnique + /// * If the string is not a valid hexadecimal string + /// * If no id could be found. + /// * If the id is not unique. pub(crate) fn update_from_ids>( be: &B, current: Vec, @@ -1194,9 +1167,7 @@ impl StringList { /// /// # Errors /// - /// * [`SnapshotFileErrorKind::NonUnicodePath`] - If a path is not valid unicode - /// - /// [`SnapshotFileErrorKind::NonUnicodePath`]: crate::error::SnapshotFileErrorKind::NonUnicodePath + /// * If a path is not valid unicode pub(crate) fn set_paths>(&mut self, paths: &[T]) -> SnapshotFileResult<()> { self.0 = paths .iter() @@ -1276,7 +1247,9 @@ impl PathList { /// * `source` - The String to parse /// /// # Errors - /// no errors can occur here + /// + /// * no errors can occur here + /// * RusticResult is used for consistency and future compatibility pub fn from_string(source: &str) -> RusticResult { Ok(Self(vec![source.into()])) } @@ -1303,11 +1276,8 @@ impl PathList { /// /// # Errors /// - /// * [`SnapshotFileErrorKind::RemovingDotsFromPathFailed`] - If removing dots from path failed - /// * [`SnapshotFileErrorKind::CanonicalizingPathFailed`] - If canonicalizing path failed - /// - /// [`SnapshotFileErrorKind::RemovingDotsFromPathFailed`]: crate::error::SnapshotFileErrorKind::RemovingDotsFromPathFailed - /// [`SnapshotFileErrorKind::CanonicalizingPathFailed`]: crate::error::SnapshotFileErrorKind::CanonicalizingPathFailed + /// * If removing dots from path failed + /// * If canonicalizing path failed pub fn sanitize(mut self) -> SnapshotFileResult { for path in &mut self.0 { *path = sanitize_dot(path)?; diff --git a/crates/core/src/repository.rs b/crates/core/src/repository.rs index 8b51a573..deb1cc52 100644 --- a/crates/core/src/repository.rs +++ b/crates/core/src/repository.rs @@ -91,7 +91,7 @@ pub struct RepositoryOptions { /// /// # Warning /// - /// Using --password can reveal the password in the process list! + /// * Using --password can reveal the password in the process list! #[cfg_attr( feature = "clap", clap(long, global = true, env = "RUSTIC_PASSWORD", hide_env_values = true) @@ -169,21 +169,15 @@ impl RepositoryOptions { /// /// # Errors /// - /// * [`RusticErrorKind::OpeningPasswordFileFailed`] - If opening the password file failed - /// * [`RusticErrorKind::ReadingPasswordFromReaderFailed`] - If reading the password failed - /// * [`RusticErrorKind::FromSplitError`] - If splitting the password command failed - /// * [`RusticErrorKind::PasswordCommandExecutionFailed`] - If executing the password command failed - /// * [`RusticErrorKind::ReadingPasswordFromCommandFailed`] - If reading the password from the command failed + /// * If opening the password file failed + /// * If reading the password failed + /// * If splitting the password command failed + /// * If executing the password command failed + /// * If reading the password from the command failed /// /// # Returns /// /// The password or `None` if no password is given - /// - /// [`RusticErrorKind::OpeningPasswordFileFailed`]: crate::error::RusticErrorKind::OpeningPasswordFileFailed - /// [`RusticErrorKind::ReadingPasswordFromReaderFailed`]: crate::error::RusticErrorKind::ReadingPasswordFromReaderFailed - /// [`RusticErrorKind::FromSplitError`]: crate::error::RusticErrorKind::FromSplitError - /// [`RusticErrorKind::PasswordCommandExecutionFailed`]: crate::error::RusticErrorKind::PasswordCommandExecutionFailed - /// [`RusticErrorKind::ReadingPasswordFromCommandFailed`]: crate::error::RusticErrorKind::ReadingPasswordFromCommandFailed pub fn evaluate_password(&self) -> RusticResult> { match (&self.password, &self.password_file, &self.password_command) { (Some(pwd), _, _) => Ok(Some(pwd.clone())), @@ -262,6 +256,7 @@ impl RepositoryOptions { /// /// # Errors /// +// TODO: Add errors pub fn read_password_from_reader(file: &mut impl BufRead) -> RusticResult { let mut password = String::new(); _ = file.read_line(&mut password).map_err(|err| { @@ -327,11 +322,9 @@ impl Repository { /// /// # Errors /// - /// * [`RusticErrorKind::NoRepositoryGiven`] - If no repository is given - /// * [`RusticErrorKind::NoIDSpecified`] - If the warm-up command does not contain `%id` - /// * [`BackendAccessErrorKind::BackendLoadError`] - If the specified backend cannot be loaded, e.g. is not supported - /// - /// [`BackendAccessErrorKind::BackendLoadError`]: crate::error::BackendAccessErrorKind::BackendLoadError + /// * If no repository is given + /// * If the warm-up command does not contain `%id` + /// * If the specified backend cannot be loaded, e.g. is not supported pub fn new(opts: &RepositoryOptions, backends: &RepositoryBackends) -> RusticResult { Self::new_with_progress(opts, backends, NoProgressBars {}) } @@ -352,13 +345,9 @@ impl

Repository { /// /// # Errors /// - /// * [`RusticErrorKind::NoRepositoryGiven`] - If no repository is given - /// * [`RusticErrorKind::NoIDSpecified`] - If the warm-up command does not contain `%id` - /// * [`BackendAccessErrorKind::BackendLoadError`] - If the specified backend cannot be loaded, e.g. is not supported - /// - /// [`RusticErrorKind::NoRepositoryGiven`]: crate::error::RusticErrorKind::NoRepositoryGiven - /// [`RusticErrorKind::NoIDSpecified`]: crate::error::RusticErrorKind::NoIDSpecified - /// [`BackendAccessErrorKind::BackendLoadError`]: crate::error::BackendAccessErrorKind::BackendLoadError + /// * If no repository is given + /// * If the warm-up command does not contain `%id` + /// * If the specified backend cannot be loaded, e.g. is not supported pub fn new_with_progress( opts: &RepositoryOptions, backends: &RepositoryBackends, @@ -405,21 +394,15 @@ impl Repository { /// /// # Errors /// - /// * [`RusticErrorKind::OpeningPasswordFileFailed`] - If opening the password file failed - /// * [`RusticErrorKind::ReadingPasswordFromReaderFailed`] - If reading the password failed - /// * [`RusticErrorKind::FromSplitError`] - If splitting the password command failed - /// * [`RusticErrorKind::PasswordCommandExecutionFailed`] - If parsing the password command failed - /// * [`RusticErrorKind::ReadingPasswordFromCommandFailed`] - If reading the password from the command failed + /// * If opening the password file failed + /// * If reading the password failed + /// * If splitting the password command failed + /// * If parsing the password command failed + /// * If reading the password from the command failed /// /// # Returns /// /// The password or `None` if no password is given - /// - /// [`RusticErrorKind::OpeningPasswordFileFailed`]: crate::error::RusticErrorKind::OpeningPasswordFileFailed - /// [`RusticErrorKind::ReadingPasswordFromReaderFailed`]: crate::error::RusticErrorKind::ReadingPasswordFromReaderFailed - /// [`RusticErrorKind::FromSplitError`]: crate::error::RusticErrorKind::FromSplitError - /// [`RusticErrorKind::PasswordCommandExecutionFailed`]: crate::error::RusticErrorKind::PasswordCommandExecutionFailed - /// [`RusticErrorKind::ReadingPasswordFromCommandFailed`]: crate::error::RusticErrorKind::ReadingPasswordFromCommandFailed pub fn password(&self) -> RusticResult> { self.opts.evaluate_password() } @@ -428,15 +411,12 @@ impl Repository { /// /// # Errors /// - /// * [`RusticErrorKind::ListingRepositoryConfigFileFailed`] - If listing the repository config file failed - /// * [`RusticErrorKind::MoreThanOneRepositoryConfig`] - If there is more than one repository config file + /// * If listing the repository config file failed + /// * If there is more than one repository config file /// /// # Returns /// /// The id of the config file or `None` if no config file is found - /// - /// [`RusticErrorKind::ListingRepositoryConfigFileFailed`]: crate::error::RusticErrorKind::ListingRepositoryConfigFileFailed - /// [`RusticErrorKind::MoreThanOneRepositoryConfig`]: crate::error::RusticErrorKind::MoreThanOneRepositoryConfig pub fn config_id(&self) -> RusticResult> { let config_ids = self.be.list(FileType::Config)?; @@ -457,35 +437,22 @@ impl Repository { /// /// # Errors /// - /// * [`RusticErrorKind::NoPasswordGiven`] - If no password is given - /// * [`RusticErrorKind::ReadingPasswordFromReaderFailed`] - If reading the password failed - /// * [`RusticErrorKind::OpeningPasswordFileFailed`] - If opening the password file failed - /// * [`RusticErrorKind::PasswordCommandExecutionFailed`] - If parsing the password command failed - /// * [`RusticErrorKind::ReadingPasswordFromCommandFailed`] - If reading the password from the command failed - /// * [`RusticErrorKind::FromSplitError`] - If splitting the password command failed - /// * [`RusticErrorKind::NoRepositoryConfigFound`] - If no repository config file is found - /// * [`RusticErrorKind::KeysDontMatchForRepositories`] - If the keys of the hot and cold backend don't match - /// * [`RusticErrorKind::IncorrectPassword`] - If the password is incorrect - /// * [`KeyFileErrorKind::NoSuitableKeyFound`] - If no suitable key is found - /// * [`RusticErrorKind::ListingRepositoryConfigFileFailed`] - If listing the repository config file failed - /// * [`RusticErrorKind::MoreThanOneRepositoryConfig`] - If there is more than one repository config file + /// * If no password is given + /// * If reading the password failed + /// * If opening the password file failed + /// * If parsing the password command failed + /// * If reading the password from the command failed + /// * If splitting the password command failed + /// * If no repository config file is found + /// * If the keys of the hot and cold backend don't match + /// * If the password is incorrect + /// * If no suitable key is found + /// * If listing the repository config file failed + /// * If there is more than one repository config file /// /// # Returns /// /// The open repository - /// - /// [`RusticErrorKind::NoPasswordGiven`]: crate::error::RusticErrorKind::NoPasswordGiven - /// [`RusticErrorKind::ReadingPasswordFromReaderFailed`]: crate::error::RusticErrorKind::ReadingPasswordFromReaderFailed - /// [`RusticErrorKind::OpeningPasswordFileFailed`]: crate::error::RusticErrorKind::OpeningPasswordFileFailed - /// [`RusticErrorKind::PasswordCommandExecutionFailed`]: crate::error::RusticErrorKind::PasswordCommandExecutionFailed - /// [`RusticErrorKind::ReadingPasswordFromCommandFailed`]: crate::error::RusticErrorKind::ReadingPasswordFromCommandFailed - /// [`RusticErrorKind::FromSplitError`]: crate::error::RusticErrorKind::FromSplitError - /// [`RusticErrorKind::NoRepositoryConfigFound`]: crate::error::RusticErrorKind::NoRepositoryConfigFound - /// [`RusticErrorKind::KeysDontMatchForRepositories`]: crate::error::RusticErrorKind::KeysDontMatchForRepositories - /// [`RusticErrorKind::IncorrectPassword`]: crate::error::RusticErrorKind::IncorrectPassword - /// [`KeyFileErrorKind::NoSuitableKeyFound`]: crate::error::KeyFileErrorKind::NoSuitableKeyFound - /// [`RusticErrorKind::ListingRepositoryConfigFileFailed`]: crate::error::RusticErrorKind::ListingRepositoryConfigFileFailed - /// [`RusticErrorKind::MoreThanOneRepositoryConfig`]: crate::error::RusticErrorKind::MoreThanOneRepositoryConfig pub fn open(self) -> RusticResult> { let password = self.password()?.ok_or_else(|| { RusticError::new( @@ -507,19 +474,12 @@ impl Repository { /// /// # Errors /// - /// * [`RusticErrorKind::NoRepositoryConfigFound`] - If no repository config file is found - /// * [`RusticErrorKind::KeysDontMatchForRepositories`] - If the keys of the hot and cold backend don't match - /// * [`RusticErrorKind::IncorrectPassword`] - If the password is incorrect - /// * [`KeyFileErrorKind::NoSuitableKeyFound`] - If no suitable key is found - /// * [`RusticErrorKind::ListingRepositoryConfigFileFailed`] - If listing the repository config file failed - /// * [`RusticErrorKind::MoreThanOneRepositoryConfig`] - If there is more than one repository config file - /// - /// [`RusticErrorKind::NoRepositoryConfigFound`]: crate::error::RusticErrorKind::NoRepositoryConfigFound - /// [`RusticErrorKind::KeysDontMatchForRepositories`]: crate::error::RusticErrorKind::KeysDontMatchForRepositories - /// [`RusticErrorKind::IncorrectPassword`]: crate::error::RusticErrorKind::IncorrectPassword - /// [`KeyFileErrorKind::NoSuitableKeyFound`]: crate::error::KeyFileErrorKind::NoSuitableKeyFound - /// [`RusticErrorKind::ListingRepositoryConfigFileFailed`]: crate::error::RusticErrorKind::ListingRepositoryConfigFileFailed - /// [`RusticErrorKind::MoreThanOneRepositoryConfig`]: crate::error::RusticErrorKind::MoreThanOneRepositoryConfig + /// * If no repository config file is found + /// * If the keys of the hot and cold backend don't match + /// * If the password is incorrect + /// * If no suitable key is found + /// * If listing the repository config file failed + /// * If there is more than one repository config file pub fn open_with_password(self, password: &str) -> RusticResult> { let config_id = self.config_id()?.ok_or_else(|| { RusticError::new( @@ -567,19 +527,12 @@ impl Repository { /// /// # Errors /// - /// * [`RusticErrorKind::NoPasswordGiven`] - If no password is given - /// * [`RusticErrorKind::ReadingPasswordFromReaderFailed`] - If reading the password failed - /// * [`RusticErrorKind::OpeningPasswordFileFailed`] - If opening the password file failed - /// * [`RusticErrorKind::PasswordCommandExecutionFailed`] - If parsing the password command failed - /// * [`RusticErrorKind::ReadingPasswordFromCommandFailed`] - If reading the password from the command failed - /// * [`RusticErrorKind::FromSplitError`] - If splitting the password command failed - /// - /// [`RusticErrorKind::NoPasswordGiven`]: crate::error::RusticErrorKind::NoPasswordGiven - /// [`RusticErrorKind::ReadingPasswordFromReaderFailed`]: crate::error::RusticErrorKind::ReadingPasswordFromReaderFailed - /// [`RusticErrorKind::OpeningPasswordFileFailed`]: crate::error::RusticErrorKind::OpeningPasswordFileFailed - /// [`RusticErrorKind::PasswordCommandExecutionFailed`]: crate::error::RusticErrorKind::PasswordCommandExecutionFailed - /// [`RusticErrorKind::ReadingPasswordFromCommandFailed`]: crate::error::RusticErrorKind::ReadingPasswordFromCommandFailed - /// [`RusticErrorKind::FromSplitError`]: crate::error::RusticErrorKind::FromSplitError + /// * If no password is given + /// * If reading the password failed + /// * If opening the password file failed + /// * If parsing the password command failed + /// * If reading the password from the command failed + /// * If splitting the password command failed pub fn init( self, key_opts: &KeyOptions, @@ -612,13 +565,9 @@ impl Repository { /// /// # Errors /// - /// * [`RusticErrorKind::ConfigFileExists`] - If a config file already exists - /// * [`RusticErrorKind::ListingRepositoryConfigFileFailed`] - If listing the repository config file failed - /// * [`RusticErrorKind::MoreThanOneRepositoryConfig`] - If there is more than one repository config file - /// - /// [`RusticErrorKind::ConfigFileExists`]: crate::error::RusticErrorKind::ConfigFileExists - /// [`RusticErrorKind::ListingRepositoryConfigFileFailed`]: crate::error::RusticErrorKind::ListingRepositoryConfigFileFailed - /// [`RusticErrorKind::MoreThanOneRepositoryConfig`]: crate::error::RusticErrorKind::MoreThanOneRepositoryConfig + /// * If a config file already exists + /// * If listing the repository config file failed + /// * If there is more than one repository config file pub fn init_with_password( self, pass: &str, @@ -744,7 +693,7 @@ impl Repository { /// /// # Errors /// - /// If files could not be listed. + /// * If files could not be listed. pub fn infos_files(&self) -> RusticResult { commands::repoinfo::collect_file_infos(self) } @@ -757,8 +706,8 @@ impl Repository { /// /// # Errors /// - /// * [`RusticErrorKind::FromSplitError`] - If the command could not be parsed. - /// * [`RusticErrorKind::FromThreadPoolbilderError`] - If the thread pool could not be created. + /// * If the command could not be parsed. + /// * If the thread pool could not be created. /// /// # Returns /// @@ -776,11 +725,8 @@ impl Repository { /// /// # Errors /// - /// * [`RusticErrorKind::FromSplitError`] - If the command could not be parsed. - /// * [`RusticErrorKind::FromThreadPoolbilderError`] - If the thread pool could not be created. - /// - /// [`RusticErrorKind::FromSplitError`]: crate::error::RusticErrorKind::FromSplitError - /// [`RusticErrorKind::FromThreadPoolbilderError`]: crate::error::RusticErrorKind::FromThreadPoolbilderError + /// * If the command could not be parsed. + /// * If the thread pool could not be created. pub fn warm_up_wait(&self, packs: impl ExactSizeIterator) -> RusticResult<()> { warm_up_wait(self, packs).map_err(|err| { RusticError::with_source(ErrorKind::Command, "Warm-up with waiting time failed.", err) @@ -855,13 +801,9 @@ impl Repository { /// /// # Errors /// - /// * [`IdErrorKind::HexError`] - If the string is not a valid hexadecimal string - /// * [`BackendAccessErrorKind::NoSuitableIdFound`] - If no id could be found. - /// * [`BackendAccessErrorKind::IdNotUnique`] - If the id is not unique. - /// - /// [`IdErrorKind::HexError`]: crate::error::IdErrorKind::HexError - /// [`BackendAccessErrorKind::NoSuitableIdFound`]: crate::error::BackendAccessErrorKind::NoSuitableIdFound - /// [`BackendAccessErrorKind::IdNotUnique`]: crate::error::BackendAccessErrorKind::IdNotUnique + /// * If the string is not a valid hexadecimal string + /// * If no id could be found. + /// * If the id is not unique. pub fn cat_file(&self, tpe: FileType, id: &str) -> RusticResult { commands::cat::cat_file(self, tpe, id) } @@ -875,9 +817,7 @@ impl Repository { /// /// # Errors /// - /// * [`CommandErrorKind::FromJsonError`] - If the key could not be serialized. - /// - /// [`CommandErrorKind::FromJsonError`]: crate::error::CommandErrorKind::FromJsonError + /// * If the key could not be serialized. pub fn add_key(&self, pass: &str, opts: &KeyOptions) -> RusticResult { add_current_key_to_repo(self, opts, pass) } @@ -890,23 +830,14 @@ impl Repository { /// /// # Errors /// - /// * [`CommandErrorKind::VersionNotSupported`] - If the version is not supported - /// * [`CommandErrorKind::CannotDowngrade`] - If the version is lower than the current version - /// * [`CommandErrorKind::NoCompressionV1Repo`] - If compression is set for a v1 repo - /// * [`CommandErrorKind::CompressionLevelNotSupported`] - If the compression level is not supported - /// * [`CommandErrorKind::SizeTooLarge`] - If the size is too large - /// * [`CommandErrorKind::MinPackSizeTolerateWrong`] - If the min packsize tolerance percent is wrong - /// * [`CommandErrorKind::MaxPackSizeTolerateWrong`] - If the max packsize tolerance percent is wrong - /// * [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`] - If the file could not be serialized to json. - /// - /// [`CommandErrorKind::VersionNotSupported`]: crate::error::CommandErrorKind::VersionNotSupported - /// [`CommandErrorKind::CannotDowngrade`]: crate::error::CommandErrorKind::CannotDowngrade - /// [`CommandErrorKind::NoCompressionV1Repo`]: crate::error::CommandErrorKind::NoCompressionV1Repo - /// [`CommandErrorKind::CompressionLevelNotSupported`]: crate::error::CommandErrorKind::CompressionLevelNotSupported - /// [`CommandErrorKind::SizeTooLarge`]: crate::error::CommandErrorKind::SizeTooLarge - /// [`CommandErrorKind::MinPackSizeTolerateWrong`]: crate::error::CommandErrorKind::MinPackSizeTolerateWrong - /// [`CommandErrorKind::MaxPackSizeTolerateWrong`]: crate::error::CommandErrorKind::MaxPackSizeTolerateWrong - /// [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`]: crate::error::CryptBackendErrorKind::SerializingToJsonByteVectorFailed + /// * If the version is not supported + /// * If the version is lower than the current version + /// * If compression is set for a v1 repo + /// * If the compression level is not supported + /// * If the size is too large + /// * If the min pack size tolerance percent is wrong + /// * If the max pack size tolerance percent is wrong + /// * If the file could not be serialized to json. pub fn apply_config(&self, opts: &ConfigOptions) -> RusticResult { commands::config::apply_config(self, opts) } @@ -957,18 +888,14 @@ impl Repository { /// /// # Errors /// - /// * [`IdErrorKind::HexError`] - If the string is not a valid hexadecimal string - /// * [`BackendAccessErrorKind::NoSuitableIdFound`] - If no id could be found. - /// * [`BackendAccessErrorKind::IdNotUnique`] - If the id is not unique. + /// * If the string is not a valid hexadecimal string + /// * If no id could be found. + /// * If the id is not unique. /// /// # Returns /// /// If `id` is (part of) an `Id`, return this snapshot. /// If `id` is "latest", return the latest snapshot respecting the giving filter. - /// - /// [`IdErrorKind::HexError`]: crate::error::IdErrorKind::HexError - /// [`BackendAccessErrorKind::NoSuitableIdFound`]: crate::error::BackendAccessErrorKind::NoSuitableIdFound - /// [`BackendAccessErrorKind::IdNotUnique`]: crate::error::BackendAccessErrorKind::IdNotUnique pub fn get_snapshot_from_str( &self, id: &str, @@ -1057,10 +984,11 @@ impl Repository { /// /// # Errors /// + // TODO: Document errors + /// /// # Note - /// The result is not sorted and may come in random order! /// - // TODO: Document errors + /// The result is not sorted and may come in random order! pub fn get_matching_snapshots( &self, filter: impl FnMut(&SnapshotFile) -> bool, @@ -1077,10 +1005,11 @@ impl Repository { /// /// # Errors /// + // TODO: Document errors + /// /// # Note - /// The result is not sorted and may come in random order! /// - // TODO: Document errors + /// The result is not sorted and may come in random order! pub fn update_matching_snapshots( &self, current: Vec, @@ -1102,7 +1031,7 @@ impl Repository { /// /// # Errors /// - /// If keep options are not valid + /// * If keep options are not valid /// /// # Returns /// @@ -1152,7 +1081,7 @@ impl Repository { /// /// # Panics /// - /// If the files could not be deleted. + /// * If the files could not be deleted. pub fn delete_snapshots(&self, ids: &[SnapshotId]) -> RusticResult<()> { if self.config().append_only == Some(true) { return Err( @@ -1175,9 +1104,7 @@ impl Repository { /// /// # Errors /// - /// * [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`] - If the file could not be serialized to json. - /// - /// [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`]: crate::error::CryptBackendErrorKind::SerializingToJsonByteVectorFailed + /// * If the file could not be serialized to json. pub fn save_snapshots(&self, mut snaps: Vec) -> RusticResult<()> { for snap in &mut snaps { snap.id = SnapshotId::default(); @@ -1196,9 +1123,10 @@ impl Repository { /// # Errors /// // TODO: Document errors + /// /// # Panics /// - /// If the error handling thread panicked + // TODO: Document panics pub fn check(&self, opts: CheckOptions) -> RusticResult<()> { let trees = self .get_all_snapshots()? @@ -1222,7 +1150,7 @@ impl Repository { // TODO: Document errors /// # Panics /// - /// If the error handling thread panicked + // TODO: Document panics pub fn check_with_trees(&self, opts: CheckOptions, trees: Vec) -> RusticResult<()> { check_repository(self, opts, trees) } @@ -1253,8 +1181,8 @@ impl Repository { /// /// # Errors /// - /// * [`CommandErrorKind::NotAllowedWithAppendOnly`] - If the repository is in append-only mode - /// * [`CommandErrorKind::NoDecision`] - If a pack has no decision + /// * If the repository is in append-only mode + /// * If a pack has no decision /// /// # Returns /// @@ -1262,6 +1190,7 @@ impl Repository { /// /// # Panics /// + // TODO: Document panics pub fn prune(&self, opts: &PruneOptions, prune_plan: PrunePlan) -> RusticResult<()> { prune_repository(self, opts, prune_plan) } @@ -1392,7 +1321,7 @@ impl Repository { /// /// # Errors /// - /// If the index could not be read. + /// * If the index could not be read. /// /// # Returns /// @@ -1409,10 +1338,11 @@ impl Repository { /// /// # Returns /// + /// An iterator over all files of the given type + /// /// # Note - /// The result is not sorted and may come in random order! /// - /// An iterator over all files of the given type + /// The result is not sorted and may come in random order! pub fn stream_files( &self, ) -> RusticResult>> { @@ -1495,7 +1425,7 @@ pub trait IndexedFull: IndexedIds { /// /// # Errors /// - /// If the blob could not be fetched from the repository. + /// * If the blob could not be fetched from the repository. /// /// # Returns /// @@ -1695,15 +1625,12 @@ impl Repository { /// /// # Errors /// - /// * [`TreeErrorKind::BlobIdNotFound`] - If the tree ID is not found in the backend. - /// * [`TreeErrorKind::DeserializingTreeFailed`] - If deserialization fails. + /// * If the tree ID is not found in the backend. + /// * If deserialization fails. /// /// # Returns /// /// The tree with the given `Id` - /// - /// [`TreeErrorKind::BlobIdNotFound`]: crate::error::TreeErrorKind::BlobIdNotFound - /// [`TreeErrorKind::DeserializingTreeFailed`]: crate::error::TreeErrorKind::DeserializingTreeFailed pub fn get_tree(&self, id: &TreeId) -> RusticResult { Tree::from_backend(self.dbe(), self.index(), *id) } @@ -1714,19 +1641,14 @@ impl Repository { /// /// # Arguments /// - /// * `root_tree` - The `Id` of the root tree - // TODO!: This ID should be a tree ID, we should refactor it to wrap it in a TreeId type + /// * `root_tree` - The `TreeId` of the root tree /// * `path` - The path /// /// # Errors /// - /// * [`TreeErrorKind::NotADirectory`] - If the path is not a directory. - /// * [`TreeErrorKind::PathNotFound`] - If the path is not found. - /// * [`TreeErrorKind::PathIsNotUtf8Conform`] - If the path is not UTF-8 conform. - /// - /// [`TreeErrorKind::NotADirectory`]: crate::error::TreeErrorKind::NotADirectory - /// [`TreeErrorKind::PathNotFound`]: crate::error::TreeErrorKind::PathNotFound - /// [`TreeErrorKind::PathIsNotUtf8Conform`]: crate::error::TreeErrorKind::PathIsNotUtf8Conform + /// * If the path is not a directory. + /// * If the path is not found. + /// * If the path is not UTF-8 conform. pub fn node_from_path(&self, root_tree: TreeId, path: &Path) -> RusticResult { Tree::node_from_path(self.dbe(), self.index(), root_tree, Path::new(path)) } @@ -1739,7 +1661,8 @@ impl Repository { /// * `path` - The path /// /// # Errors - /// if loading trees from the backend fails + /// + /// * If loading trees from the backend fails pub fn find_nodes_from_path( &self, ids: impl IntoIterator, @@ -1756,7 +1679,8 @@ impl Repository { /// * `matches` - The matching criterion /// /// # Errors - /// if loading trees from the backend fails + /// + /// * If loading trees from the backend fails pub fn find_matching_nodes( &self, ids: impl IntoIterator, @@ -1790,13 +1714,9 @@ impl Repository { /// /// # Errors /// - /// * [`IdErrorKind::HexError`] - If the string is not a valid hexadecimal string - /// * [`BackendAccessErrorKind::NoSuitableIdFound`] - If no id could be found. - /// * [`BackendAccessErrorKind::IdNotUnique`] - If the id is not unique. - /// - /// [`IdErrorKind::HexError`]: crate::error::IdErrorKind::HexError - /// [`BackendAccessErrorKind::NoSuitableIdFound`]: crate::error::BackendAccessErrorKind::NoSuitableIdFound - /// [`BackendAccessErrorKind::IdNotUnique`]: crate::error::BackendAccessErrorKind::IdNotUnique + /// * If the string is not a valid hexadecimal string + /// * If no id could be found. + /// * If the id is not unique. pub fn node_from_snapshot_path( &self, snap_path: &str, @@ -1991,13 +1911,11 @@ impl Repository { /// /// # Errors /// - /// * [`IndexErrorKind::BlobInIndexNotFound`] - If the blob is not found in the index + /// * If the blob is not found in the index /// /// # Returns /// /// The cached blob in bytes. - /// - /// [`IndexErrorKind::BlobInIndexNotFound`]: crate::error::IndexErrorKind::BlobInIndexNotFound pub fn get_blob_cached(&self, id: &BlobId, tpe: BlobType) -> RusticResult { self.get_blob_or_insert_with(id, || self.index().blob_from_backend(self.dbe(), tpe, id)) } @@ -2025,13 +1943,11 @@ impl Repository { /// /// # Errors /// - /// * [`IdErrorKind::HexError`] - If the string is not a valid hexadecimal string + /// * If the string is not a valid hexadecimal string /// /// # Returns /// /// The raw blob in bytes. - /// - /// [`IdErrorKind::HexError`]: crate::error::IdErrorKind::HexError pub fn cat_blob(&self, tpe: BlobType, id: &str) -> RusticResult { commands::cat::cat_blob(self, tpe, id) } @@ -2042,16 +1958,14 @@ impl Repository { /// /// * `node` - The node to dump /// * `w` - The writer to use - /// - /// # Note - /// - /// Currently, only regular file nodes are supported. /// /// # Errors /// - /// * [`CommandErrorKind::DumpNotSupported`] - If the node is not a file. + /// * If the node is not a file. + /// + /// # Note /// - /// [`CommandErrorKind::DumpNotSupported`]: crate::error::CommandErrorKind::DumpNotSupported + /// Currently, only regular file nodes are supported. pub fn dump(&self, node: &Node, w: &mut impl Write) -> RusticResult<()> { commands::dump::dump(self, node, w) } @@ -2071,15 +1985,12 @@ impl Repository { /// /// # Errors /// - /// * [`CommandErrorKind::ErrorCreating`] - If a directory could not be created. - /// * [`CommandErrorKind::ErrorCollecting`] - If the restore information could not be collected. + /// * If a directory could not be created. + /// * If the restore information could not be collected. /// /// # Returns /// /// The restore plan. - /// - /// [`CommandErrorKind::ErrorCreating`]: crate::error::CommandErrorKind::ErrorCreating - /// [`CommandErrorKind::ErrorCollecting`]: crate::error::CommandErrorKind::ErrorCollecting pub fn prepare_restore( &self, opts: &RestoreOptions, @@ -2132,7 +2043,7 @@ impl Repository { /// /// # Warning /// - /// If you remove the original snapshots, you may loose data! + /// * If you remove the original snapshots, you may loose data! /// /// # Errors /// diff --git a/crates/core/src/repository/command_input.rs b/crates/core/src/repository/command_input.rs index fd424f2f..63e897b0 100644 --- a/crates/core/src/repository/command_input.rs +++ b/crates/core/src/repository/command_input.rs @@ -127,7 +127,7 @@ impl CommandInput { /// /// # Errors /// - /// `CommandInputErrorKind` if return status cannot be read + /// * If return status cannot be read pub fn run(&self, context: &str, what: &str) -> RusticResult<()> { if !self.is_set() { trace!("not calling command {context}:{what} - not set"); diff --git a/crates/core/src/repository/warm_up.rs b/crates/core/src/repository/warm_up.rs index a28d969d..05b33d43 100644 --- a/crates/core/src/repository/warm_up.rs +++ b/crates/core/src/repository/warm_up.rs @@ -37,11 +37,8 @@ pub(super) mod constants { /// /// # Errors /// -/// * [`RepositoryErrorKind::FromSplitError`] - If the command could not be parsed. -/// * [`RepositoryErrorKind::FromThreadPoolbilderError`] - If the thread pool could not be created. -/// -/// [`RepositoryErrorKind::FromSplitError`]: crate::error::RepositoryErrorKind::FromSplitError -/// [`RepositoryErrorKind::FromThreadPoolbilderError`]: crate::error::RepositoryErrorKind::FromThreadPoolbilderError +/// * If the command could not be parsed. +/// * If the thread pool could not be created. pub(crate) fn warm_up_wait( repo: &Repository, packs: impl ExactSizeIterator, @@ -64,11 +61,8 @@ pub(crate) fn warm_up_wait( /// /// # Errors /// -/// * [`RepositoryErrorKind::FromSplitError`] - If the command could not be parsed. -/// * [`RepositoryErrorKind::FromThreadPoolbilderError`] - If the thread pool could not be created. -/// -/// [`RepositoryErrorKind::FromSplitError`]: crate::error::RepositoryErrorKind::FromSplitError -/// [`RepositoryErrorKind::FromThreadPoolbilderError`]: crate::error::RepositoryErrorKind::FromThreadPoolbilderError +/// * If the command could not be parsed. +/// * If the thread pool could not be created. pub(crate) fn warm_up( repo: &Repository, packs: impl ExactSizeIterator, @@ -91,9 +85,7 @@ pub(crate) fn warm_up( /// /// # Errors /// -/// * [`RepositoryErrorKind::FromSplitError`] - If the command could not be parsed. -/// -/// [`RepositoryErrorKind::FromSplitError`]: crate::error::RepositoryErrorKind::FromSplitError +/// * If the command could not be parsed. fn warm_up_command( packs: impl ExactSizeIterator, command: &CommandInput, @@ -139,9 +131,7 @@ fn warm_up_command( /// /// # Errors /// -/// * [`RepositoryErrorKind::FromThreadPoolbilderError`] - If the thread pool could not be created. -/// -/// [`RepositoryErrorKind::FromThreadPoolbilderError`]: crate::error::RepositoryErrorKind::FromThreadPoolbilderError +/// * If the thread pool could not be created. fn warm_up_repo( repo: &Repository, packs: impl ExactSizeIterator, diff --git a/crates/core/src/vfs.rs b/crates/core/src/vfs.rs index 1f5ad9fe..56d9e854 100644 --- a/crates/core/src/vfs.rs +++ b/crates/core/src/vfs.rs @@ -95,15 +95,12 @@ impl VfsTree { /// /// # Errors /// - /// * [`VfsErrorKind::OnlyNormalPathsAreAllowed`] if the path is not a normal path - /// * [`VfsErrorKind::DirectoryExistsAsNonVirtual`] if the path is a directory in the repository + /// * If the path is not a normal path + /// * If the path is a directory in the repository /// /// # Returns /// /// `Ok(())` if the tree was added successfully - /// - /// [`VfsErrorKind::DirectoryExistsAsNonVirtual`]: crate::error::VfsErrorKind::DirectoryExistsAsNonVirtual - /// [`VfsErrorKind::OnlyNormalPathsAreAllowed`]: crate::error::VfsErrorKind::OnlyNormalPathsAreAllowed fn add_tree(&mut self, path: &Path, new_tree: Self) -> VfsResult<()> { let mut tree = self; let mut components = path.components(); @@ -203,7 +200,7 @@ impl Vfs { /// /// # Panics /// - /// If the node is not a directory + /// * If the node is not a directory #[must_use] pub fn from_dir_node(node: &Node) -> Self { let tree = VfsTree::RusticTree(node.subtree.unwrap()); @@ -222,11 +219,8 @@ impl Vfs { /// /// # Errors /// - /// * [`VfsErrorKind::OnlyNormalPathsAreAllowed`] if the path is not a normal path - /// * [`VfsErrorKind::DirectoryExistsAsNonVirtual`] if the path is a directory in the repository - /// - /// [`VfsErrorKind::DirectoryExistsAsNonVirtual`]: crate::error::VfsErrorKind::DirectoryExistsAsNonVirtual - /// [`VfsErrorKind::OnlyNormalPathsAreAllowed`]: crate::error::VfsErrorKind::OnlyNormalPathsAreAllowed + /// * If the path is not a normal path + /// * If the path is a directory in the repository pub fn from_snapshots( mut snapshots: Vec, path_template: &str, @@ -353,13 +347,12 @@ impl Vfs { /// /// # Errors /// - /// * [`VfsErrorKind::NameDoesNotExist`] - if the component name doesn't exist + /// * If the component name doesn't exist /// /// # Returns /// /// The [`Node`] at the specified path /// - /// [`VfsErrorKind::NameDoesNotExist`]: crate::error::VfsErrorKind::NameDoesNotExist /// [`Tree`]: crate::repofile::Tree pub fn node_from_path( &self, @@ -400,18 +393,17 @@ impl Vfs { /// /// # Errors /// - /// * [`VfsErrorKind::NameDoesNotExist`] - if the component name doesn't exist + /// * If the component name doesn't exist /// /// # Returns /// /// The list of [`Node`]s at the specified path /// - /// [`VfsErrorKind::NameDoesNotExist`]: crate::error::VfsErrorKind::NameDoesNotExist /// [`Tree`]: crate::repofile::Tree /// /// # Panics /// - /// Panics if the path is not a directory. + /// * Panics if the path is not a directory. pub fn dir_entries_from_path( &self, repo: &Repository, @@ -510,7 +502,7 @@ impl OpenFile { /// /// # Panics /// - /// Panics if the `Node` has no content + /// * Panics if the `Node` has no content pub fn from_node(repo: &Repository, node: &Node) -> Self { let mut start = 0; let mut content: Vec<_> = node diff --git a/crates/core/src/vfs/webdavfs.rs b/crates/core/src/vfs/webdavfs.rs index 1813b612..8495162c 100644 --- a/crates/core/src/vfs/webdavfs.rs +++ b/crates/core/src/vfs/webdavfs.rs @@ -84,7 +84,7 @@ impl WebDavFS { /// /// # Errors /// - /// * [`FsError::GeneralFailure`] - If the [`Tree`] could not be found + /// * If the [`Tree`] could not be found /// /// # Returns /// @@ -106,7 +106,7 @@ impl WebDavFS { /// /// # Errors /// - /// * [`FsError::GeneralFailure`] - If the [`Tree`] could not be found + /// * If the [`Tree`] could not be found /// /// # Returns /// From b31054c26c82c8e96a547217f773856f40659c72 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 04:17:48 +0100 Subject: [PATCH 064/129] Cleanup errors Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/archiver.rs | 12 +--- crates/core/src/archiver/parent.rs | 8 +-- crates/core/src/backend.rs | 55 +-------------- crates/core/src/backend/local_destination.rs | 9 +-- crates/core/src/chunker.rs | 8 --- crates/core/src/commands.rs | 44 ------------ crates/core/src/error.rs | 72 -------------------- crates/core/src/index.rs | 19 +----- crates/core/src/repofile/snapshotfile.rs | 2 +- crates/core/src/repository/warm_up.rs | 10 --- crates/core/src/vfs/format.rs | 18 ++--- 11 files changed, 23 insertions(+), 234 deletions(-) diff --git a/crates/core/src/archiver.rs b/crates/core/src/archiver.rs index 337c5465..420f8d52 100644 --- a/crates/core/src/archiver.rs +++ b/crates/core/src/archiver.rs @@ -25,17 +25,9 @@ use crate::{ Progress, }; -/// [`ArchiverErrorKind`] describes the errors that can be returned from the archiver #[derive(thiserror::Error, Debug, displaydoc::Display)] -#[non_exhaustive] -pub enum ArchiverErrorKind { - /// tree stack empty - TreeStackEmpty, - /// couldn't determine size for item in Archiver - CouldNotDetermineSize, -} - -pub(crate) type ArchiverResult = Result; +/// Tree stack empty +pub struct TreeStackEmptyError; /// The `Archiver` is responsible for archiving files and trees. /// It will read the file, chunk it, and write the chunks to the backend. diff --git a/crates/core/src/archiver/parent.rs b/crates/core/src/archiver/parent.rs index e0e15719..f807ce9d 100644 --- a/crates/core/src/archiver/parent.rs +++ b/crates/core/src/archiver/parent.rs @@ -6,7 +6,7 @@ use std::{ use log::warn; use crate::{ - archiver::{tree::TreeType, ArchiverErrorKind, ArchiverResult}, + archiver::{tree::TreeType, TreeStackEmptyError}, backend::{decrypt::DecryptReadBackend, node::Node}, blob::tree::{Tree, TreeId}, index::ReadGlobalIndex, @@ -218,8 +218,8 @@ impl Parent { /// # Errors /// /// * If the tree stack is empty. - fn finish_dir(&mut self) -> ArchiverResult<()> { - let (tree, node_idx) = self.stack.pop().ok_or(ArchiverErrorKind::TreeStackEmpty)?; + fn finish_dir(&mut self) -> Result<(), TreeStackEmptyError> { + let (tree, node_idx) = self.stack.pop().ok_or(TreeStackEmptyError)?; self.tree = tree; self.node_idx = node_idx; @@ -252,7 +252,7 @@ impl Parent { be: &impl DecryptReadBackend, index: &impl ReadGlobalIndex, item: TreeType, - ) -> ArchiverResult> { + ) -> Result, TreeStackEmptyError> { let result = match item { TreeType::NewTree((path, node, tree)) => { let parent_result = self diff --git a/crates/core/src/backend.rs b/crates/core/src/backend.rs index 9f46dd36..67abe40d 100644 --- a/crates/core/src/backend.rs +++ b/crates/core/src/backend.rs @@ -10,7 +10,7 @@ pub(crate) mod node; pub(crate) mod stdin; pub(crate) mod warm_up; -use std::{io::Read, num::TryFromIntError, ops::Deref, path::PathBuf, sync::Arc}; +use std::{io::Read, ops::Deref, path::PathBuf, sync::Arc}; use bytes::Bytes; use enum_map::Enum; @@ -31,64 +31,11 @@ use crate::{ #[derive(thiserror::Error, Debug, displaydoc::Display)] #[non_exhaustive] pub enum BackendErrorKind { - /// backend `{0:?}` is not supported! - BackendNotSupported(String), - /// no suitable id found for `{0}` - NoSuitableIdFound(String), - /// id `{0}` is not unique - IdNotUnique(String), - /// creating data in backend failed - CreatingDataOnBackendFailed, - /// writing bytes to backend failed - WritingBytesToBackendFailed, - /// removing data from backend failed - RemovingDataFromBackendFailed, - /// failed to list files on Backend - ListingFilesOnBackendFailed, /// Path is not allowed: `{0:?}` PathNotAllowed(PathBuf), - /// Backend location not convertible: `{location}` - BackendLocationNotConvertible { location: String }, -} - -/// [`CryptBackendErrorKind`] describes the errors that can be returned by a Decryption action in Backends -#[derive(thiserror::Error, Debug, displaydoc::Display)] -#[non_exhaustive] -pub enum CryptBackendErrorKind { - /// decryption not supported for backend - DecryptionNotSupportedForBackend, - /// length of uncompressed data does not match! - LengthOfUncompressedDataDoesNotMatch, - /// failed to read encrypted data during full read - DecryptionInFullReadFailed, - /// failed to read encrypted data during partial read - DecryptionInPartialReadFailed, - /// decrypting from backend failed - DecryptingFromBackendFailed, - /// deserializing from bytes of JSON Text failed: `{0:?}` - DeserializingFromBytesOfJsonTextFailed(serde_json::Error), - /// failed to write data in crypt backend - WritingDataInCryptBackendFailed, - /// failed to list Ids - ListingIdsInDecryptionBackendFailed, - /// writing full hash failed in `CryptBackend` - WritingFullHashFailed, - /// decoding Zstd compressed data failed: `{0:?}` - DecodingZstdCompressedDataFailed(std::io::Error), - /// Serializing to JSON byte vector failed: `{0:?}` - SerializingToJsonByteVectorFailed(serde_json::Error), - /// encrypting data failed - EncryptingDataFailed, - /// Compressing and appending data failed: `{0:?}` - CopyEncodingDataFailed(std::io::Error), - /// conversion for integer failed: `{0:?}` - IntConversionFailed(TryFromIntError), - /// Extra verification failed: After decrypting and decompressing the data changed! - ExtraVerificationFailed, } pub(crate) type BackendResult = Result; -pub(crate) type CryptBackendResult = Result; /// All [`FileType`]s which are located in separated directories pub const ALL_FILE_TYPES: [FileType; 4] = [ diff --git a/crates/core/src/backend/local_destination.rs b/crates/core/src/backend/local_destination.rs index 69627bf2..1166230f 100644 --- a/crates/core/src/backend/local_destination.rs +++ b/crates/core/src/backend/local_destination.rs @@ -299,7 +299,7 @@ impl LocalDestination { /// # Errors /// /// * If the user/group could not be set. - #[allow(clippy::unused_self)] + #[allow(clippy::unused_self, clippy::unnecessary_wraps)] pub(crate) fn set_user_group( &self, _item: impl AsRef, @@ -355,7 +355,7 @@ impl LocalDestination { /// # Errors /// /// * If the uid/gid could not be set. - #[allow(clippy::unused_self)] + #[allow(clippy::unused_self, clippy::unnecessary_wraps)] pub(crate) fn set_uid_gid( &self, _item: impl AsRef, @@ -403,7 +403,7 @@ impl LocalDestination { /// # Errors /// /// * If the permissions could not be set. - #[allow(clippy::unused_self)] + #[allow(clippy::unused_self, clippy::unnecessary_wraps)] pub(crate) fn set_permission( &self, _item: impl AsRef, @@ -456,7 +456,7 @@ impl LocalDestination { /// # Errors /// /// * If the extended attributes could not be set. - #[allow(clippy::unused_self)] + #[allow(clippy::unused_self, clippy::unnecessary_wraps)] pub(crate) fn set_extended_attributes( &self, _item: impl AsRef, @@ -600,6 +600,7 @@ impl LocalDestination { /// # Returns /// /// Ok if the special file was created. + #[allow(clippy::unused_self, clippy::unnecessary_wraps)] pub(crate) fn create_special( &self, _item: impl AsRef, diff --git a/crates/core/src/chunker.rs b/crates/core/src/chunker.rs index 82f775a3..57b4149e 100644 --- a/crates/core/src/chunker.rs +++ b/crates/core/src/chunker.rs @@ -10,14 +10,6 @@ use crate::{ error::{ErrorKind, RusticError, RusticResult}, }; -/// [`PolynomialErrorKind`] describes the errors that can happen while dealing with Polynomials -#[derive(thiserror::Error, Debug, displaydoc::Display)] -#[non_exhaustive] -pub enum PolynomialErrorKind { - /// no suitable polynomial found - NoSuitablePolynomialFound, -} - pub(super) mod constants { /// The Splitmask is used to determine if a chunk is a chunk boundary. pub(super) const SPLITMASK: u64 = (1u64 << 20) - 1; diff --git a/crates/core/src/commands.rs b/crates/core/src/commands.rs index 61c79cf0..e4d15a12 100644 --- a/crates/core/src/commands.rs +++ b/crates/core/src/commands.rs @@ -1,11 +1,5 @@ //! The commands that can be run by the CLI. -use std::{num::TryFromIntError, path::PathBuf}; - -use chrono::OutOfRangeError; - -use crate::{backend::node::NodeType, blob::BlobId, repofile::packfile::PackId, RusticError}; - pub mod backup; /// The `cat` command. pub mod cat; @@ -26,41 +20,3 @@ pub mod repair; pub mod repoinfo; pub mod restore; pub mod snapshots; - -/// [`CommandErrorKind`] describes the errors that can happen while executing a high-level command -#[derive(thiserror::Error, Debug, displaydoc::Display)] -#[non_exhaustive] -pub enum CommandErrorKind { - /// path is no dir: `{0}` - PathIsNoDir(String), - /// used blobs are missing: blob `{0}` doesn't existing - BlobsMissing(BlobId), - /// used pack `{0}`: size does not match! Expected size: `{1}`, real size: `{2}` - PackSizeNotMatching(PackId, u32, u32), - /// used pack `{0}` does not exist! - PackNotExisting(PackId), - /// pack `{0}` got no decision what to do - NoDecision(PackId), - /// Bytesize parser failed: `{0}` - FromByteSizeParser(String), - /// --repack-uncompressed makes no sense for v1 repo! - RepackUncompressedRepoV1, - /// datetime out of range: `{0}` - FromOutOfRangeError(OutOfRangeError), - /// node type `{0:?}` not supported by dump - DumpNotSupported(NodeType), - /// error creating `{0:?}`: `{1:?}` - ErrorCreating(PathBuf, Box), - /// error collecting information for `{0:?}`: `{1:?}` - ErrorCollecting(PathBuf, Box), - /// error setting length for `{0:?}`: `{1:?}` - ErrorSettingLength(PathBuf, Box), - /// Conversion from integer failed: `{0:?}` - ConversionFromIntFailed(TryFromIntError), - /// Specify one of the keep-* options for forget! Please use keep-none to keep no snapshot. - NoKeepOption, - /// Checking the repository failed! - CheckFailed, -} - -pub(crate) type CommandResult = Result; diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 08e4fd78..c767e637 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -413,76 +413,4 @@ pub enum ErrorKind { Verification, /// Virtual File System Error Vfs, - // /// The repository password is incorrect. Please try again. - // IncorrectRepositoryPassword, - // /// No repository given. Please use the --repository option. - // NoRepositoryGiven, - // /// No password given. Please use one of the --password-* options. - // NoPasswordGiven, - // /// warm-up command must contain %id! - // NoIDSpecified, - // /// error opening password file `{0:?}` - // OpeningPasswordFileFailed(std::io::Error), - // /// No repository config file found. Is there a repo at `{0}`? - // NoRepositoryConfigFound(String), - // /// More than one repository config file at `{0}`. Aborting. - // MoreThanOneRepositoryConfig(String), - // /// keys from repo and repo-hot do not match for `{0}`. Aborting. - // KeysDontMatchForRepositories(String), - // /// repository is a hot repository!\nPlease use as --repo-hot in combination with the normal repo. Aborting. - // HotRepositoryFlagMissing, - // /// repo-hot is not a hot repository! Aborting. - // IsNotHotRepository, - // /// incorrect password! - // IncorrectPassword, - // /// error running the password command - // PasswordCommandExecutionFailed, - // /// error reading password from command - // ReadingPasswordFromCommandFailed, - // /// running command `{0}`:`{1}` was not successful: `{2}` - // CommandExecutionFailed(String, String, std::io::Error), - // /// running command {0}:{1} returned status: `{2}` - // CommandErrorStatus(String, String, ExitStatus), - // /// error listing the repo config file - // ListingRepositoryConfigFileFailed, - // /// error listing the repo keys - // ListingRepositoryKeysFailed, - // /// error listing the hot repo keys - // ListingHotRepositoryKeysFailed, - // /// error accessing config file - // AccessToConfigFileFailed, - // /// Thread pool build error: `{0:?}` - // FromThreadPoolbilderError(rayon::ThreadPoolBuildError), - // /// reading Password failed: `{0:?}` - // ReadingPasswordFromReaderFailed(std::io::Error), - // /// reading Password from prompt failed: `{0:?}` - // ReadingPasswordFromPromptFailed(std::io::Error), - // /// Config file already exists. Aborting. - // ConfigFileExists, - // /// did not find id `{0}` in index - // IdNotFound(BlobId), - // /// no suitable backend type found - // NoBackendTypeGiven, - // /// Hex decoding error: `{0:?}` - // HexError(hex::FromHexError), } - -// TODO: Possible more general categories for errors for RusticErrorKind (WIP): -// -// - **JSON Parsing Errors**: e.g., `serde_json::Error` -// - **Version Errors**: e.g., `VersionNotSupported`, `CannotDowngrade` -// - **Compression Errors**: e.g., `NoCompressionV1Repo`, `CompressionLevelNotSupported` -// - **Size Errors**: e.g., `SizeTooLarge` -// - **File and Path Errors**: e.g., `ErrorCreating`, `ErrorCollecting`, `ErrorSettingLength` -// - **Thread Pool Errors**: e.g., `rayon::ThreadPoolBuildError` -// - **Conversion Errors**: e.g., `ConversionFromIntFailed` -// - **Permission Errors**: e.g., `NotAllowedWithAppendOnly` -// - **Parsing Errors**: e.g., `shell_words::ParseError` -// - **Cryptographic Errors**: e.g., `DataDecryptionFailed`, `DataEncryptionFailed`, `CryptoKeyTooShort` -// - **Polynomial Errors**: e.g., `NoSuitablePolynomialFound` -// - **File Handling Errors**: e.g., `TransposingOptionResultFailed`, `ConversionFromU64ToUsizeFailed` -// - **ID Processing Errors**: e.g., `HexError` -// - **Repository Errors**: general repository-related errors -// - **Backend Access Errors**: e.g., `BackendNotSupported`, `BackendLoadError`, `NoSuitableIdFound`, `IdNotUnique` -// - **Rclone Errors**: e.g., `NoOutputForRcloneVersion`, `NoStdOutForRclone`, `RCloneExitWithBadStatus` -// - **REST API Errors**: e.g., `NotSupportedForRetry`, `UrlParsingFailed` diff --git a/crates/core/src/index.rs b/crates/core/src/index.rs index 505216b7..65d13940 100644 --- a/crates/core/src/index.rs +++ b/crates/core/src/index.rs @@ -4,7 +4,7 @@ use bytes::Bytes; use derive_more::Constructor; use crate::{ - backend::{decrypt::DecryptReadBackend, CryptBackendErrorKind, FileType}, + backend::{decrypt::DecryptReadBackend, FileType}, blob::{tree::TreeId, BlobId, BlobType, DataId}, error::{ErrorKind, RusticError, RusticResult}, index::binarysorted::{Index, IndexCollector, IndexType}, @@ -18,23 +18,6 @@ use crate::{ pub(crate) mod binarysorted; pub(crate) mod indexer; -/// [`IndexErrorKind`] describes the errors that can be returned by processing Indizes -#[derive(thiserror::Error, Debug, displaydoc::Display)] -#[non_exhaustive] -pub enum IndexErrorKind { - /// blob not found in index - BlobInIndexNotFound, - /// failed to get a blob from the backend - GettingBlobIndexEntryFromBackendFailed, - /// saving `IndexFile` failed - SavingIndexFileFailed { - /// the error that occurred - source: CryptBackendErrorKind, - }, -} - -pub(crate) type IndexResult = Result; - /// An entry in the index #[derive(Debug, Clone, Copy, PartialEq, Eq, Constructor)] pub struct IndexEntry { diff --git a/crates/core/src/repofile/snapshotfile.rs b/crates/core/src/repofile/snapshotfile.rs index 8b779059..e6aef643 100644 --- a/crates/core/src/repofile/snapshotfile.rs +++ b/crates/core/src/repofile/snapshotfile.rs @@ -1249,7 +1249,7 @@ impl PathList { /// # Errors /// /// * no errors can occur here - /// * RusticResult is used for consistency and future compatibility + /// * [`RusticResult`] is used for consistency and future compatibility pub fn from_string(source: &str) -> RusticResult { Ok(Self(vec![source.into()])) } diff --git a/crates/core/src/repository/warm_up.rs b/crates/core/src/repository/warm_up.rs index 05b33d43..ff63529d 100644 --- a/crates/core/src/repository/warm_up.rs +++ b/crates/core/src/repository/warm_up.rs @@ -13,16 +13,6 @@ use crate::{ CommandInput, }; -/// [`WarmupErrorKind`] describes the errors that can be returned from Warmup -#[derive(thiserror::Error, Debug, displaydoc::Display)] -#[non_exhaustive] -pub enum WarmupErrorKind { - /// Error in warm-up command - General, -} - -pub(crate) type WarmupResult = Result; - pub(super) mod constants { /// The maximum number of reader threads to use for warm-up. pub(super) const MAX_READER_THREADS_NUM: usize = 20; diff --git a/crates/core/src/vfs/format.rs b/crates/core/src/vfs/format.rs index b3f6dee1..91f26eb6 100644 --- a/crates/core/src/vfs/format.rs +++ b/crates/core/src/vfs/format.rs @@ -9,15 +9,15 @@ use runtime_format::{FormatKey, FormatKeyError}; /// To be formatted with [`runtime_format`]. /// /// The following keys are available: -/// - `id`: the snapshot id -/// - `long_id`: the snapshot id as a string -/// - `time`: the snapshot time -/// - `username`: the snapshot username -/// - `hostname`: the snapshot hostname -/// - `label`: the snapshot label -/// - `tags`: the snapshot tags -/// - `backup_start`: the snapshot backup start time -/// - `backup_end`: the snapshot backup end time +/// * `id`: the snapshot id +/// * `long_id`: the snapshot id as a string +/// * `time`: the snapshot time +/// * `username`: the snapshot username +/// * `hostname`: the snapshot hostname +/// * `label`: the snapshot label +/// * `tags`: the snapshot tags +/// * `backup_start`: the snapshot backup start time +/// * `backup_end`: the snapshot backup end time #[derive(Debug)] pub(crate) struct FormattedSnapshot<'a> { /// The snapshot file. From de1c6e43a35cc329119a58215ee772283de9c024 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 04:18:43 +0100 Subject: [PATCH 065/129] fix import Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/commands/repair/index.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/core/src/commands/repair/index.rs b/crates/core/src/commands/repair/index.rs index 55b21fad..7c44774c 100644 --- a/crates/core/src/commands/repair/index.rs +++ b/crates/core/src/commands/repair/index.rs @@ -9,12 +9,11 @@ use crate::{ decrypt::{DecryptReadBackend, DecryptWriteBackend}, FileType, ReadBackend, WriteBackend, }, - error::{ErrorKind, RusticResult}, + error::{ErrorKind, RusticError, RusticResult}, index::{binarysorted::IndexCollector, indexer::Indexer, GlobalIndex}, progress::{Progress, ProgressBars}, repofile::{packfile::PackId, IndexFile, IndexPack, PackHeader, PackHeaderRef}, repository::{Open, Repository}, - RusticError, }; #[cfg_attr(feature = "clap", derive(clap::Parser))] From 2548568b6cf674f34655725372dbee5fd11919f8 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 04:30:46 +0100 Subject: [PATCH 066/129] fix reference for fixme Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/backend/ignore.rs | 3 +-- crates/core/src/blob/tree.rs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/core/src/backend/ignore.rs b/crates/core/src/backend/ignore.rs index 0e8d8048..61a0f371 100644 --- a/crates/core/src/backend/ignore.rs +++ b/crates/core/src/backend/ignore.rs @@ -187,8 +187,7 @@ impl LocalSource { let mut override_builder = OverrideBuilder::new(""); // FIXME: Refactor this to a function to be reused - // This is the same of backend::ignore::Localsource::new - // https://github.com/rustic-rs/rustic_core/blob/db82ed21db158e66ef4f8f3e6ba8c8b52d2fd42a/crates/core/src/blob/tree.rs#L630 + // This is the same of `tree::NodeStreamer::new_with_glob()` for g in &filter_opts.globs { _ = override_builder .add(g) diff --git a/crates/core/src/blob/tree.rs b/crates/core/src/blob/tree.rs index a19eb429..67167f09 100644 --- a/crates/core/src/blob/tree.rs +++ b/crates/core/src/blob/tree.rs @@ -581,6 +581,7 @@ where recursive, }) } + /// Creates a new `NodeStreamer` with glob patterns. /// /// # Arguments @@ -603,8 +604,7 @@ where let mut override_builder = OverrideBuilder::new(""); // FIXME: Refactor this to a function to be reused - // This is the same of backend::ignore::Localsource::new - // https://github.com/rustic-rs/rustic_core/blob/db82ed21db158e66ef4f8f3e6ba8c8b52d2fd42a/crates/core/src/backend/ignore.rs#L184 + // This is the same of `backend::ignore::Localsource::new` for g in &opts.glob { _ = override_builder .add(g) From 10e5886d6ec7da4dff543a9b90cb438dc4408f36 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 04:30:58 +0100 Subject: [PATCH 067/129] fix visibility Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/archiver/file_archiver.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/core/src/archiver/file_archiver.rs b/crates/core/src/archiver/file_archiver.rs index 867f6a72..4fe48792 100644 --- a/crates/core/src/archiver/file_archiver.rs +++ b/crates/core/src/archiver/file_archiver.rs @@ -57,7 +57,7 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> FileArchiver<'a, BE, I> { /// /// * If sending the message to the raw packer fails. /// * If converting the data length to u64 fails - pub fn new( + pub(crate) fn new( be: BE, index: &'a I, indexer: SharedIndexer, From 4e19c263446b50c00cc7b7c587bcf4ba4c4a1541 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 04:42:53 +0100 Subject: [PATCH 068/129] add error handling rules to error module Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/error.rs | 43 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index c767e637..ab39b15c 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -1,4 +1,47 @@ //! Error types and Result module. +//! +//! ## Error handling rules +//! +//! ### Visibility +//! +//! All `pub fn` (associated) functions need to return a `Result (==RusticResult)`, if they are fallible. +//! As they are user facing and will cross the API boundary we need to make sure they are high-quality errors containing all +//! needed information and actionable guidance. +//! +//! `pub(crate) fn` visibility should use a local error and thus a Result and error type limited in visibility, e.g. +//! `pub(crate) type ArchiverResult = Result`. +//! +//! ### Downgrading +//! +//! `RusticError`s should **not** be downgraded, instead we **upgrade** the function signature to contain a `RusticResult`. +//! For instance, if a function returns `Result` and we discover an error path that contains a `RusticError`, +//! we don't need to convert that into an `ArchiverErrorKind`, we should change the function signature, so it returns either a +//! `Result (==RusticResult)` or nested results like `RusticResult>`. +//! So even if the visibility of that function is `fn` or `pub(crate) fn` it should return a `RusticResult` containing a `RusticError`. +//! +//! ### Conversion and Nested Results +//! +//! Converting between different error kinds or their variants e.g. `TreeErrorKind::Channel` -> `ArchiverErrorKind::Channel` +//! should seldom happen (probably never?), as the caller is most likely not setup to handle such errors from a different layer, +//! so at this point, we should return either a `RusticError` indicating this is a hard error. Or use a nested Result, e.g. +//! `Result, RusticError>`. +//! +//! Local error types in `pub fn` (associated) functions need to be manually converted into a `RusticError` with a good error message +//! and other important information, e.g. actionable guidance for the user. +//! +//! ### Backend traits +//! +//! By using `RusticResult` in our `Backend` traits, we also make sure, we get back presentable errors for our users. +//! We had them before as type erased errors, that we just bubbled up. Now we can provide more context and guidance. +//! +//! ### Traits +//! +//! All traits and implementations of (foreign) traits should use `RusticResult` as return type or `Box` as `Self::Err`. +//! +//! ### Display and Debug +//! +//! All types that we want to attach to an error should implement `Display` and `Debug` to provide a good error message and a nice way +//! to display the error. // FIXME: Remove when 'displaydoc' has fixed/recommended further treatment upstream: https://github.com/yaahc/displaydoc/issues/48 #![allow(clippy::doc_markdown)] From a0ee05d7908ecb4cd2edead21fdeac0f1d0c1022 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 04:52:51 +0100 Subject: [PATCH 069/129] use `is_code` Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/error.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index ab39b15c..892302ca 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -212,16 +212,9 @@ impl RusticError { .map_or(false, |c| c.as_str() == code) } - /// Expose the inner error kind. - /// - /// This is useful for matching on the error kind. - pub fn into_inner(self) -> ErrorKind { - self.kind - } - /// Checks if the error is due to an incorrect password pub fn is_incorrect_password(&self) -> bool { - matches!(self.error_code.as_deref(), Some("C002")) + self.is_code("C002") } /// Creates a new error from a given error. From e7f073d1477cb988bcf1137a39d2b3c9b70e90c5 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 04:54:02 +0100 Subject: [PATCH 070/129] style: cargo fmt Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/blob/tree.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/core/src/blob/tree.rs b/crates/core/src/blob/tree.rs index 67167f09..6dffa397 100644 --- a/crates/core/src/blob/tree.rs +++ b/crates/core/src/blob/tree.rs @@ -581,7 +581,7 @@ where recursive, }) } - + /// Creates a new `NodeStreamer` with glob patterns. /// /// # Arguments From abc55886ed239e52f29b06a8ef35a64cba74353c Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 05:06:29 +0100 Subject: [PATCH 071/129] imports in backend Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/choose.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/backend/src/choose.rs b/crates/backend/src/choose.rs index bc992620..4dad066a 100644 --- a/crates/backend/src/choose.rs +++ b/crates/backend/src/choose.rs @@ -1,11 +1,9 @@ //! This module contains [`BackendOptions`] and helpers to choose a backend from a given url. use derive_setters::Setters; -use rustic_core::{ErrorKind, RusticError}; use std::{collections::HashMap, sync::Arc}; use strum_macros::{Display, EnumString}; -#[allow(unused_imports)] -use rustic_core::{RepositoryBackends, RusticResult, WriteBackend}; +use rustic_core::{ErrorKind, RepositoryBackends, RusticError, RusticResult, WriteBackend}; use crate::{ local::LocalBackend, From 80bbcecc9f222d61a8a0a9494529e695da68e31b Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 05:13:32 +0100 Subject: [PATCH 072/129] cleanup backend crate Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/local.rs | 52 +------------------------------------ crates/backend/src/rest.rs | 28 +++++--------------- 2 files changed, 8 insertions(+), 72 deletions(-) diff --git a/crates/backend/src/local.rs b/crates/backend/src/local.rs index 04cbdcc5..c7ed376e 100644 --- a/crates/backend/src/local.rs +++ b/crates/backend/src/local.rs @@ -1,9 +1,8 @@ use std::{ fs::{self, File}, io::{Read, Seek, SeekFrom, Write}, - num::TryFromIntError, path::{Path, PathBuf}, - process::{Command, ExitStatus}, + process::Command, }; use aho_corasick::AhoCorasick; @@ -16,55 +15,6 @@ use rustic_core::{ ALL_FILE_TYPES, }; -/// [`LocalBackendErrorKind`] describes the errors that can be returned by an action on the filesystem in Backends -#[derive(thiserror::Error, Debug, displaydoc::Display)] -#[non_exhaustive] -pub enum LocalBackendErrorKind { - /// directory creation failed: `{0:?}` - DirectoryCreationFailed(std::io::Error), - /// querying metadata failed: `{0:?}` - QueryingMetadataFailed(std::io::Error), - /// querying `WalkDir` metadata failed: `{0:?}` - QueryingWalkDirMetadataFailed(walkdir::Error), - /// execution of command failed: `{0:?}` - CommandExecutionFailed(std::io::Error), - /// command was not successful for filename `{file_name}`, type `{file_type}`, id `{id}`: `{status}` - CommandNotSuccessful { - /// File name - file_name: String, - /// File type - file_type: String, - /// Item ID - id: String, - /// Exit status - status: ExitStatus, - }, - /// error building automaton `{0:?}` - FromAhoCorasick(aho_corasick::BuildError), - /// `{0:?}` - #[error(transparent)] - FromTryIntError(TryFromIntError), - /// `{0:?}` - #[error(transparent)] - FromWalkdirError(walkdir::Error), - /// removing file failed: `{0:?}` - FileRemovalFailed(std::io::Error), - /// opening file failed: `{0:?}` - OpeningFileFailed(std::io::Error), - /// setting file length failed: `{0:?}` - SettingFileLengthFailed(std::io::Error), - /// can't jump to position in file: `{0:?}` - CouldNotSeekToPositionInFile(std::io::Error), - /// couldn't write to buffer: `{0:?}` - CouldNotWriteToBuffer(std::io::Error), - /// reading file contents failed: `{0:?}` - ReadingContentsOfFileFailed(std::io::Error), - /// reading exact length of file contents failed: `{0:?}` - ReadingExactLengthOfFileFailed(std::io::Error), - /// failed to sync OS Metadata to disk: `{0:?}` - SyncingOfOsMetadataFailed(std::io::Error), -} - /// A local backend. #[derive(Clone, Debug)] pub struct LocalBackend { diff --git a/crates/backend/src/rest.rs b/crates/backend/src/rest.rs index 63309229..cd6d7e41 100644 --- a/crates/backend/src/rest.rs +++ b/crates/backend/src/rest.rs @@ -3,7 +3,6 @@ use std::time::Duration; use backoff::{backoff::Backoff, ExponentialBackoff, ExponentialBackoffBuilder}; use bytes::Bytes; -use displaydoc::Display; use log::{trace, warn}; use reqwest::{ blocking::{Client, ClientBuilder, Response}, @@ -11,23 +10,12 @@ use reqwest::{ Url, }; use serde::Deserialize; -use thiserror::Error; use rustic_core::{ErrorKind, FileType, Id, ReadBackend, RusticError, RusticResult, WriteBackend}; -/// [`RestErrorKind`] describes the errors that can be returned while dealing with the REST API -#[derive(Error, Debug, Display)] -#[non_exhaustive] -pub enum RestErrorKind { - #[cfg(feature = "rest")] - /// requesting resource failed: `{0:?}` - RequestingResourceFailed(reqwest::Error), - #[cfg(feature = "rest")] - /// backoff failed: {0:?} - BackoffError(backoff::Error), - /// joining URL failed on: {0} - JoiningUrlFailed(url::ParseError), -} +/// joining URL failed on: `{0}` +#[derive(thiserror::Error, Clone, Copy, Debug, displaydoc::Display)] +pub struct JoiningUrlFailedError(url::ParseError); pub(super) mod constants { use std::time::Duration; @@ -240,8 +228,8 @@ impl RestBackend { /// /// # Errors /// - /// * If the url could not be created. - fn url(&self, tpe: FileType, id: &Id) -> Result { + /// * If the url could not be joined/created. + fn url(&self, tpe: FileType, id: &Id) -> Result { let id_path = if tpe == FileType::Config { "config".to_string() } else { @@ -252,9 +240,7 @@ impl RestBackend { path }; - self.url - .join(&id_path) - .map_err(RestErrorKind::JoiningUrlFailed) + self.url.join(&id_path).map_err(JoiningUrlFailedError) } } @@ -438,7 +424,7 @@ fn construct_backoff_error(err: backoff::Error) -> Box Date: Sun, 27 Oct 2024 05:16:09 +0100 Subject: [PATCH 073/129] re-export for error handling Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/backend/src/lib.rs b/crates/backend/src/lib.rs index 60a36440..49eec804 100644 --- a/crates/backend/src/lib.rs +++ b/crates/backend/src/lib.rs @@ -84,3 +84,6 @@ pub use crate::{ choose::{BackendOptions, SupportedBackend}, local::LocalBackend, }; + +// re-export for error handling +pub use rustic_core::{ErrorKind, RusticError, RusticResult, Severity, Status}; From 6ed4adfb289b175168eebc8ef897c3e00b8f9317 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 14:32:50 +0100 Subject: [PATCH 074/129] add back description of errors in fn docs Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/local.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/crates/backend/src/local.rs b/crates/backend/src/local.rs index c7ed376e..e785d9bf 100644 --- a/crates/backend/src/local.rs +++ b/crates/backend/src/local.rs @@ -108,7 +108,10 @@ impl LocalBackend { /// /// # Errors /// - // TODO: Add error types + /// * If the patterns could not be compiled. + /// * If the command could not be parsed. + /// * If the command could not be executed. + /// * If the command was not successful. /// /// # Notes /// @@ -321,7 +324,10 @@ impl ReadBackend for LocalBackend { /// /// # Errors /// - // TODO: Add error types + /// * If the file could not be opened. + /// * If the file could not be sought to the given position. + /// * If the length of the file could not be converted to u32. + /// * If the exact length of the file could not be read. fn read_partial( &self, tpe: FileType, @@ -429,7 +435,11 @@ impl WriteBackend for LocalBackend { /// /// # Errors /// - // TODO: Add error types + /// * If the file could not be opened. + /// * If the length of the bytes could not be converted to u64. + /// * If the length of the file could not be set. + /// * If the bytes could not be written to the file. + /// * If the OS Metadata could not be synced to disk. fn write_bytes( &self, tpe: FileType, From 51f6db54e4fae15b363380a0c0eb1560d1b890d9 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 15:04:59 +0100 Subject: [PATCH 075/129] fix path Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/opendal.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/backend/src/opendal.rs b/crates/backend/src/opendal.rs index f220e8bb..8d3dd30d 100644 --- a/crates/backend/src/opendal.rs +++ b/crates/backend/src/opendal.rs @@ -228,11 +228,11 @@ impl ReadBackend for OpenDALBackend { ); } - let path = &(tpe.dirname().to_string() + "/"); + let path = tpe.dirname().to_string() + "/"; Ok(self .operator - .list_with(path) + .list_with(&path) .recursive(true) .call() .map_err(|err| { From ce4aca7001326e01bde9c9e61a2fe232841d6520 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 15:11:10 +0100 Subject: [PATCH 076/129] warn about errors in listing size before throwing out the errors Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/local.rs | 5 +++++ crates/backend/src/opendal.rs | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/backend/src/local.rs b/crates/backend/src/local.rs index e785d9bf..cc3221e0 100644 --- a/crates/backend/src/local.rs +++ b/crates/backend/src/local.rs @@ -282,6 +282,11 @@ impl ReadBackend for LocalBackend { )?, )) }) + .inspect(|r| { + if let Err(err) = r { + warn!("Error while listing files: {:?}", err); + } + }) .filter_map(RusticResult::ok); Ok(walker.collect()) diff --git a/crates/backend/src/opendal.rs b/crates/backend/src/opendal.rs index 8d3dd30d..d21b98ab 100644 --- a/crates/backend/src/opendal.rs +++ b/crates/backend/src/opendal.rs @@ -3,7 +3,7 @@ use std::{collections::HashMap, str::FromStr, sync::OnceLock}; use bytes::Bytes; use bytesize::ByteSize; -use log::trace; +use log::{trace, warn}; use opendal::{ layers::{BlockingLayer, ConcurrentLimitLayer, LoggingLayer, RetryLayer, ThrottleLayer}, BlockingOperator, Metakey, Operator, Scheme, @@ -317,6 +317,11 @@ impl ReadBackend for OpenDALBackend { )?, )) }) + .inspect(|r| { + if let Err(err) = r { + warn!("Error while listing files: {:?}", err); + } + }) .filter_map(RusticResult::ok) .collect()) } From 5abc9234cb16aacf7491b6fd151ecf43010fd1cf Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 15:13:56 +0100 Subject: [PATCH 077/129] format closure Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/opendal.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/crates/backend/src/opendal.rs b/crates/backend/src/opendal.rs index d21b98ab..b21f3e2c 100644 --- a/crates/backend/src/opendal.rs +++ b/crates/backend/src/opendal.rs @@ -409,15 +409,14 @@ impl WriteBackend for OpenDALBackend { .to_string() + "/"; - self.operator.create_dir(&path) - .map_err(|err| - RusticError::with_source( - ErrorKind::Backend, - "Creating directory failed in the backend. Please check if the given path is correct.", - err, - ) - .attach_context("location", self.location()) - .attach_context("path", path) + self.operator.create_dir(&path).map_err(|err| + RusticError::with_source( + ErrorKind::Backend, + "Creating directory failed in the backend. Please check if the given path is correct.", + err, + ) + .attach_context("location", self.location()) + .attach_context("path", path) ) })?; From 1a5a53607ce1d429b1473a5d93e4eac939e2c313 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Oct 2024 15:31:47 +0100 Subject: [PATCH 078/129] Attach error code to verification error Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/backend/decrypt.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/core/src/backend/decrypt.rs b/crates/core/src/backend/decrypt.rs index ee0e0c61..785d8292 100644 --- a/crates/core/src/backend/decrypt.rs +++ b/crates/core/src/backend/decrypt.rs @@ -437,8 +437,8 @@ impl DecryptBackend { return Err( RusticError::new( ErrorKind::Verification, - "Extra verification failed: After decrypting and decompressing the data changed! The data may be corrupted.\nPlease check the backend for corruption and try again. You can also try to run `rustic check --read-data` to check for corruption. This may take a long time.", - ) + "Verification failed: After decrypting and decompressing the data changed! The data may be corrupted.\nPlease check the backend for corruption and try again. You can also try to run `rustic check --read-data` to check for corruption. This may take a long time.", + ).attach_error_code("C003") ); } } @@ -491,8 +491,8 @@ impl DecryptBackend { return Err( RusticError::new( ErrorKind::Verification, - "Extra verification failed: After decrypting and decompressing the data changed! The data may be corrupted.\nPlease check the backend for corruption and try again. You can also try to run `rustic check --read-data` to check for corruption. This may take a long time.", - ) + "Verification failed: After decrypting and decompressing the data changed! The data may be corrupted.\nPlease check the backend for corruption and try again. You can also try to run `rustic check --read-data` to check for corruption. This may take a long time.", + ).attach_error_code("C003") ); } } From 6745db47b46120a9ab1c04dcf7c1224bee31c170 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 28 Oct 2024 14:38:30 +0100 Subject: [PATCH 079/129] Use Cow Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/error.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 892302ca..68602555 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -52,6 +52,7 @@ use ::std::convert::Into; use smol_str::SmolStr; use std::{ backtrace::Backtrace, + borrow::Cow, fmt::{self, Display}, }; @@ -77,7 +78,7 @@ pub struct RusticError { guidance: SmolStr, /// The context of the error. - context: Box<[(&'static str, SmolStr)]>, + context: Cow<'static, [(&'static str, SmolStr)]>, /// The URL of the documentation for the error. docs_url: Option, @@ -168,7 +169,7 @@ impl RusticError { Box::new(Self { kind, guidance: guidance.into().into(), - context: Box::default(), + context: Cow::default(), source: None, error_code: None, docs_url: None, @@ -191,7 +192,7 @@ impl RusticError { Box::new(Self { kind, guidance: guidance.into().into(), - context: Box::default(), + context: Cow::default(), source: Some(source.into()), error_code: None, docs_url: None, @@ -225,7 +226,7 @@ impl RusticError { Box::new(Self { kind, guidance: error.to_string().into(), - context: Box::default(), + context: Cow::default(), source: Some(Box::new(error)), error_code: None, docs_url: None, @@ -297,9 +298,9 @@ impl RusticError { // IMPORTANT: This is manually implemented to allow for multiple contexts to be added. /// Attach context to the error. pub fn attach_context(mut self, key: &'static str, value: impl Into) -> Box { - let mut context = self.context.to_vec(); + let mut context = self.context.into_owned(); context.push((key, value.into())); - self.context = context.into_boxed_slice(); + self.context = Cow::from(context); Box::new(self) } @@ -309,9 +310,9 @@ impl RusticError { /// /// This should not be used in most cases, as it will overwrite any existing contexts. /// Rather use `attach_context` for multiple contexts. - pub fn overwrite_context(self, value: impl Into>) -> Box { + pub fn overwrite_context(self, value: impl Into>) -> Box { Box::new(Self { - context: value.into(), + context: Cow::from(value.into()), ..self }) } From 9d694b2235982b919407ef76647728ae897b1cee Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 28 Oct 2024 14:45:12 +0100 Subject: [PATCH 080/129] Use SmolStr for `RusticError::new()` Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/error.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 68602555..1c0430d5 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -165,10 +165,10 @@ impl Display for RusticError { // Accessors for anything we do want to expose publicly. impl RusticError { /// Creates a new error with the given kind and guidance. - pub fn new(kind: ErrorKind, guidance: impl Into) -> Box { + pub fn new(kind: ErrorKind, guidance: impl Into) -> Box { Box::new(Self { kind, - guidance: guidance.into().into(), + guidance: guidance.into(), context: Cow::default(), source: None, error_code: None, From 5d90807d8a5dc04161f5bfa12a246aca0affc333 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 28 Oct 2024 14:45:25 +0100 Subject: [PATCH 081/129] Add context Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/choose.rs | 9 ++++++--- crates/backend/src/util.rs | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/backend/src/choose.rs b/crates/backend/src/choose.rs index 4dad066a..ad8d1009 100644 --- a/crates/backend/src/choose.rs +++ b/crates/backend/src/choose.rs @@ -114,14 +114,17 @@ impl BackendOptions { repo_string .map(|string| { let (be_type, location) = location_to_type_and_path(string)?; - be_type.to_backend(location, options.into()).map_err(|err| { - RusticError::with_source( + be_type + .to_backend(location.clone(), options.into()) + .map_err(|err| { + RusticError::with_source( ErrorKind::Backend, "Could not load the backend. Please check the given backend and try again.", err, ) .attach_context("name", be_type.to_string()) - }) + .attach_context("location", location.to_string()) + }) }) .transpose() } diff --git a/crates/backend/src/util.rs b/crates/backend/src/util.rs index ea46edba..cd59856e 100644 --- a/crates/backend/src/util.rs +++ b/crates/backend/src/util.rs @@ -2,7 +2,7 @@ use crate::SupportedBackend; use rustic_core::{ErrorKind, RusticError, RusticResult}; /// A backend location. This is a string that represents the location of the backend. -#[derive(PartialEq, Eq, Debug)] +#[derive(PartialEq, Eq, Debug, Clone)] pub struct BackendLocation(String); impl std::ops::Deref for BackendLocation { From 645304980dacc4eaff45a8cacf65cb32f8c2ec7d Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 28 Oct 2024 21:52:50 +0100 Subject: [PATCH 082/129] remove check issues Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/commands/check.rs | 98 ------------------------------- 1 file changed, 98 deletions(-) diff --git a/crates/core/src/commands/check.rs b/crates/core/src/commands/check.rs index e52338d9..3832fcc1 100644 --- a/crates/core/src/commands/check.rs +++ b/crates/core/src/commands/check.rs @@ -3,7 +3,6 @@ use std::{ collections::{BTreeSet, HashMap}, fmt::Debug, num::ParseIntError, - path::PathBuf, str::FromStr, }; @@ -34,103 +33,6 @@ use crate::{ ErrorKind, TreeId, }; -#[non_exhaustive] -#[derive(Debug, displaydoc::Display)] -pub enum CheckIssueKind { - /// error reading pack `{id}` - ErrorReadingPack { id: PackId }, - /// cold file for hot file Type: `{file_type:?}`, Id: `{id}` does not exist - NoColdFile { id: Id, file_type: FileType }, - /// Type: `{file_type:?}`, Id: `{id}`: hot size: `{size_hot}`, actual size: `{size}` - HotFileSizeMismatch { - id: Id, - file_type: FileType, - size_hot: u32, - size: u32, - }, - /// hot file Type: `{file_type:?}`, Id: {id} is missing! - NoHotFile { id: Id, file_type: FileType }, - /// Error reading cached file Type: `{file_type:?}`, Id: `{id}` - ErrorReadingCache { id: Id, file_type: FileType }, - /// Error reading file Type: `{file_type:?}`, Id: `{id}` - ErrorReadingFile { id: Id, file_type: FileType }, - /// Cached file Type: `{file_type:?}`, Id: `{id}` is not identical to backend! - CacheMismatch { id: Id, file_type: FileType }, - /// pack `{id}`: No time is set! Run prune to correct this! - PackTimeNotSet { id: PackId }, - /// pack `{id}`: blob `{blob_id}` blob type does not match: type: `{blob_type:?}`, expected: `{expected:?}` - PackBlobTypesMismatch { - id: PackId, - blob_id: BlobId, - blob_type: BlobType, - expected: BlobType, - }, - /// pack `{id}`: blob `{blob_id}` offset in index: `{offset}`, expected: `{expected}` - PackBlobOffsetMismatch { - id: PackId, - blob_id: BlobId, - offset: u32, - expected: u32, - }, - /// pack `{id}` not referenced in index. Can be a parallel backup job. To repair: 'rustic repair index'. - PackNotReferenced { id: Id }, - /// pack `{id}`: size computed by index: `{index_size}`, actual size: {size}. To repair: 'rustic repair index'. - PackSizeMismatchIndex { id: Id, index_size: u32, size: u32 }, - /// pack `{id}` is referenced by the index but not present! To repair: 'rustic repair index'." - NoPack { id: PackId }, - /// file `{file:?}` doesn't have a content - FileHasNoContent { file: PathBuf }, - /// file `{file:?}` blob `{blob_num}` has null ID - FileBlobHasNullId { file: PathBuf, blob_num: usize }, - /// file `{file:?}` blob `{blob_id}` is missing in index - FileBlobNotInIndex { file: PathBuf, blob_id: Id }, - /// dir `{dir:?}` doesn't have a subtree - NoSubTree { dir: PathBuf }, - /// "dir `{dir:?}` subtree has null ID - NullSubTree { dir: PathBuf }, - /// pack `{id}`: data size does not match expected size. Read: `{size}` bytes, expected: `{expected}` bytes - PackSizeMismatch { - id: PackId, - size: usize, - expected: usize, - }, - /// pack `{id}`: Hash mismatch. Computed hash: `{computed}` - PackHashMismatch { id: PackId, computed: PackId }, - /// pack `{id}`: Header length in pack file doesn't match index. In pack: `{length}`, computed: `{computed}` - PackHeaderLengthMismatch { - id: PackId, - length: u32, - computed: u32, - }, - /// pack `{id}`: Header from pack file does not match the index - PackHeaderMismatchIndex { id: PackId }, - /// pack `{id}`, blob `{blob_id}`: Actual uncompressed length does not fit saved uncompressed length - PackBlobLengthMismatch { id: PackId, blob_id: BlobId }, - /// pack `{id}`, blob `{blob_id}`: Hash mismatch. Computed hash: `{computed}` - PackBlobHashMismatch { - id: PackId, - blob_id: BlobId, - computed: BlobId, - }, -} - -#[derive(Debug, Default)] -pub struct CheckIssues(Vec); - -impl std::ops::DerefMut for CheckIssues { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl std::ops::Deref for CheckIssues { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - #[derive(Clone, Copy, Debug, Default)] #[non_exhaustive] /// Options to specify which subset of packs will be read From ff507539c735f95399b921dff52d24f2e6a25250 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 28 Oct 2024 21:56:14 +0100 Subject: [PATCH 083/129] change to unsupported error kind in dump Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/commands/dump.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/core/src/commands/dump.rs b/crates/core/src/commands/dump.rs index e235d827..4c48c91a 100644 --- a/crates/core/src/commands/dump.rs +++ b/crates/core/src/commands/dump.rs @@ -30,7 +30,7 @@ pub(crate) fn dump( ) -> RusticResult<()> { if node.node_type != NodeType::File { return Err(RusticError::new( - ErrorKind::Command, + ErrorKind::Unsupported, "Dump is not supported for non-file node types. You could try to use `cat` instead.", ) .attach_context("node type", node.node_type.to_string())); From 73baca4aaf455af34db26eef3df390972a705ef7 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 28 Oct 2024 21:58:24 +0100 Subject: [PATCH 084/129] change to invalid input error kind in forget Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/commands/forget.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/core/src/commands/forget.rs b/crates/core/src/commands/forget.rs index 744ea3a5..b5fc0a83 100644 --- a/crates/core/src/commands/forget.rs +++ b/crates/core/src/commands/forget.rs @@ -524,7 +524,7 @@ impl KeepOptions { ) -> RusticResult> { if !self.is_valid() { return Err(RusticError::new( - ErrorKind::Command, + ErrorKind::InvalidInput, "No valid keep options specified, please make sure to specify at least one keep-* option.", )); } From dd2c079d0901ad476becce3ec9bb00c6b6810b22 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 28 Oct 2024 22:02:42 +0100 Subject: [PATCH 085/129] specify context better Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/commands/merge.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/core/src/commands/merge.rs b/crates/core/src/commands/merge.rs index 7e239628..b4a68ac5 100644 --- a/crates/core/src/commands/merge.rs +++ b/crates/core/src/commands/merge.rs @@ -119,7 +119,7 @@ pub(crate) fn merge_trees( "Failed to convert chunk length to u64.", err, ) - .attach_context("chunk", chunk.len().to_string()) + .attach_context("chunk length", chunk.len().to_string()) })?; if !index.has_tree(&new_id) { From e34d827f347f6da2adedadd430b988d658a14b89 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 28 Oct 2024 22:05:39 +0100 Subject: [PATCH 086/129] attach context for pack_read_header Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/commands/repair/index.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/core/src/commands/repair/index.rs b/crates/core/src/commands/repair/index.rs index 7c44774c..a57c484e 100644 --- a/crates/core/src/commands/repair/index.rs +++ b/crates/core/src/commands/repair/index.rs @@ -198,6 +198,10 @@ pub(crate) fn index_checked_from_collector( "Failed to convert pack_read_header length to u64.", err, ) + .attach_context( + "pack read header length", + pack_read_header.len().to_string(), + ) })?); let index_packs: Vec<_> = pack_read_header From 58720c8666218d892820d1d2c9f0087dcaca30db Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 28 Oct 2024 22:06:57 +0100 Subject: [PATCH 087/129] attach context for pack_read_header Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/commands/repair/index.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/core/src/commands/repair/index.rs b/crates/core/src/commands/repair/index.rs index a57c484e..ede58d99 100644 --- a/crates/core/src/commands/repair/index.rs +++ b/crates/core/src/commands/repair/index.rs @@ -84,6 +84,10 @@ pub(crate) fn repair_index( "Failed to convert pack_read_header length to u64.", err, ) + .attach_context( + "pack read header length", + pack_read_header.len().to_string(), + ) })?); for (id, size_hint, packsize) in pack_read_header { debug!("reading pack {id}..."); From 897a33942ed8b924f044af705db397bc92d69dd7 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 28 Oct 2024 22:10:34 +0100 Subject: [PATCH 088/129] remove error variants for snapshot file Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/repofile/snapshotfile.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/crates/core/src/repofile/snapshotfile.rs b/crates/core/src/repofile/snapshotfile.rs index e6aef643..53231e27 100644 --- a/crates/core/src/repofile/snapshotfile.rs +++ b/crates/core/src/repofile/snapshotfile.rs @@ -35,24 +35,12 @@ use clap::ValueHint; #[derive(thiserror::Error, Debug, displaydoc::Display)] #[non_exhaustive] pub enum SnapshotFileErrorKind { - /// non-unicode hostname `{0:?}` - NonUnicodeHostname(OsString), /// non-unicode path `{0:?}` NonUnicodePath(PathBuf), - /// no snapshots found - NoSnapshotsFound, /// value `{0:?}` not allowed ValueNotAllowed(String), /// datetime out of range: `{0:?}` OutOfRange(OutOfRangeError), - /// getting the `SnapshotFile` from the backend failed - GettingSnapshotFileFailed, - /// getting the `SnapshotFile` by ID failed - GettingSnapshotFileByIdFailed, - /// unpacking `SnapshotFile` result failed - UnpackingSnapshotFileResultFailed, - /// collecting IDs failed: `{0:?}` - FindingIdsFailed(Vec), /// removing dots from paths failed: `{0:?}` RemovingDotsFromPathFailed(std::io::Error), /// canonicalizing path failed: `{0:?}` From dcfa5eea12e1b4d5c118d3a184c4c47c6eabe6d5 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 28 Oct 2024 22:13:40 +0100 Subject: [PATCH 089/129] AppendOnly errors Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/commands/prune.rs | 4 ++-- crates/core/src/commands/repair/index.rs | 4 ++-- crates/core/src/commands/repair/snapshots.rs | 4 ++-- crates/core/src/error.rs | 2 ++ 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/core/src/commands/prune.rs b/crates/core/src/commands/prune.rs index 417c222c..0a34af3d 100644 --- a/crates/core/src/commands/prune.rs +++ b/crates/core/src/commands/prune.rs @@ -1230,8 +1230,8 @@ pub(crate) fn prune_repository( ) -> RusticResult<()> { if repo.config().append_only == Some(true) { return Err(RusticError::new( - ErrorKind::Repository, - "Repository is in append-only mode, pruning is not allowed. Aborting.", + ErrorKind::AppendOnly, + "Pruning is not allowed in append-only repositories. Please disable append-only mode first, if you know what you are doing. Aborting.", )); } repo.warm_up_wait(prune_plan.repack_packs().into_iter())?; diff --git a/crates/core/src/commands/repair/index.rs b/crates/core/src/commands/repair/index.rs index ede58d99..a869cf81 100644 --- a/crates/core/src/commands/repair/index.rs +++ b/crates/core/src/commands/repair/index.rs @@ -46,8 +46,8 @@ pub(crate) fn repair_index( if repo.config().append_only == Some(true) { return Err( RusticError::new( - ErrorKind::Repository, - "index repair is not allowed in append-only repositories. Please disable append-only mode first, if you know what you are doing.", + ErrorKind::AppendOnly, + "Repairing the index is not allowed in append-only repositories. Please disable append-only mode first, if you know what you are doing. Aborting.", ) ); } diff --git a/crates/core/src/commands/repair/snapshots.rs b/crates/core/src/commands/repair/snapshots.rs index 154e18b4..fd57864f 100644 --- a/crates/core/src/commands/repair/snapshots.rs +++ b/crates/core/src/commands/repair/snapshots.rs @@ -100,8 +100,8 @@ pub(crate) fn repair_snapshots( if opts.delete && config_file.append_only == Some(true) { return Err( RusticError::new( - ErrorKind::Repository, - "snapshot removal is not allowed in append-only repositories. Please disable append-only mode first, if you know what you are doing.", + ErrorKind::AppendOnly, + "Removing snapshots is not allowed in append-only repositories. Please disable append-only mode first, if you know what you are doing. Aborting.", ) ); } diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 1c0430d5..7fdd3022 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -413,6 +413,8 @@ pub enum Status { #[non_exhaustive] #[derive(thiserror::Error, Debug, displaydoc::Display)] pub enum ErrorKind { + /// Append-only mode is enabled + AppendOnly, /// Backend Error Backend, /// Command Error From 21528b83c621a803340ef48c4e2ae93b98c15123 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 28 Oct 2024 22:20:40 +0100 Subject: [PATCH 090/129] cleanup error in configfile Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/repofile/configfile.rs | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/crates/core/src/repofile/configfile.rs b/crates/core/src/repofile/configfile.rs index 6e86153c..77998c1c 100644 --- a/crates/core/src/repofile/configfile.rs +++ b/crates/core/src/repofile/configfile.rs @@ -1,5 +1,3 @@ -use std::num::ParseIntError; - use serde_derive::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -12,26 +10,6 @@ use crate::{ repofile::RepoFile, }; -/// [`ConfigFileErrorKind`] describes the errors that can be returned for `ConfigFile`s -#[derive(thiserror::Error, Debug, displaydoc::Display)] -#[non_exhaustive] -pub enum ConfigFileErrorKind { - /// config version not supported: {version}, compression: {compression:?} - ConfigVersionNotSupported { - /// The version of the config - version: u32, - /// The compression level - compression: Option, - }, - /// Parsing failed for polynomial: {polynomial} : {source} - ParsingFailedForPolynomial { - polynomial: String, - source: ParseIntError, - }, -} - -pub(crate) type ConfigFileResult = Result; - pub(super) mod constants { pub(super) const KB: u32 = 1024; From fbf4c4fde888268889920ab748fd121d38c98a3c Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 28 Oct 2024 22:20:47 +0100 Subject: [PATCH 091/129] order imports Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/repofile/snapshotfile.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/core/src/repofile/snapshotfile.rs b/crates/core/src/repofile/snapshotfile.rs index 53231e27..9ae42721 100644 --- a/crates/core/src/repofile/snapshotfile.rs +++ b/crates/core/src/repofile/snapshotfile.rs @@ -1,13 +1,14 @@ use std::{ cmp::Ordering, collections::{BTreeMap, BTreeSet}, - ffi::OsString, fmt::{self, Display}, path::{Path, PathBuf}, str::FromStr, }; use chrono::{DateTime, Duration, Local, OutOfRangeError}; +#[cfg(feature = "clap")] +use clap::ValueHint; use derivative::Derivative; use derive_setters::Setters; use dunce::canonicalize; @@ -28,9 +29,6 @@ use crate::{ Id, }; -#[cfg(feature = "clap")] -use clap::ValueHint; - /// [`SnapshotFileErrorKind`] describes the errors that can be returned for `SnapshotFile`s #[derive(thiserror::Error, Debug, displaydoc::Display)] #[non_exhaustive] From 2b8caa4035a0ce41d4d08071adec629d14a08f5c Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 28 Oct 2024 22:22:52 +0100 Subject: [PATCH 092/129] cleanup variants in KeyFileErrorKind Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/repofile/keyfile.rs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/crates/core/src/repofile/keyfile.rs b/crates/core/src/repofile/keyfile.rs index 93743a20..cb18c679 100644 --- a/crates/core/src/repofile/keyfile.rs +++ b/crates/core/src/repofile/keyfile.rs @@ -15,25 +15,6 @@ use crate::{ #[derive(thiserror::Error, Debug, displaydoc::Display)] #[non_exhaustive] pub enum KeyFileErrorKind { - /// listing `KeyFiles` failed - ListingKeyFilesFailed, - /// couldn't get `KeyFile` from backend - CouldNotGetKeyFileFromBackend, - /// `serde_json` couldn't deserialize the data for the key: `{key_id:?}` : `{source}` - DeserializingFromSliceForKeyIdFailed { - /// The id of the key - key_id: KeyId, - /// The error that occurred - source: serde_json::Error, - }, - /// `serde_json` couldn't serialize the data into a JSON byte vector: `{0:?}` - CouldNotSerializeAsJsonByteVector(serde_json::Error), - /// output length is invalid: `{0:?}` - OutputLengthInvalid(scrypt::errors::InvalidOutputLen), - /// invalid scrypt parameters: `{0:?}` - InvalidSCryptParameters(scrypt::errors::InvalidParams), - /// deserializing master key from slice failed: `{source}` - DeserializingMasterKeyFromSliceFailed { source: serde_json::Error }, /// conversion from `{from}` to `{to}` failed for `{x}` : `{source}` ConversionFailed { from: &'static str, From 80c1ade1f8063c4a834986a9ff7854c16f1a8897 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 28 Oct 2024 22:26:19 +0100 Subject: [PATCH 093/129] cleanup variants in PackerErrorKind Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/blob/packer.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/core/src/blob/packer.rs b/crates/core/src/blob/packer.rs index abebf013..a058cf7a 100644 --- a/crates/core/src/blob/packer.rs +++ b/crates/core/src/blob/packer.rs @@ -32,8 +32,6 @@ use crate::{ #[derive(thiserror::Error, Debug, displaydoc::Display)] #[non_exhaustive] pub enum PackerErrorKind { - /// getting total size failed - GettingTotalSize, /// Conversion from `{from}` to `{to}` failed: `{source}` Conversion { to: &'static str, From 858eb2e8ad3bee2480f2345cb46ee6c44ac99259 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 28 Oct 2024 22:26:42 +0100 Subject: [PATCH 094/129] cleanup variants in LocalDestinationErrorKind Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/backend/local_destination.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/core/src/backend/local_destination.rs b/crates/core/src/backend/local_destination.rs index 1166230f..b89d036f 100644 --- a/crates/core/src/backend/local_destination.rs +++ b/crates/core/src/backend/local_destination.rs @@ -52,10 +52,7 @@ pub enum LocalDestinationErrorKind { length: u64, source: TryFromIntError, }, - /// [`walkdir::Error`] - #[error(transparent)] - FromWalkdirError(walkdir::Error), - /// [`Errno`] + /// `{0}` #[error(transparent)] #[cfg(not(windows))] FromErrnoError(Errno), From 2bfa13cc37d73c00ee3b7e741b22ee3dd90fbd41 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 28 Oct 2024 22:27:44 +0100 Subject: [PATCH 095/129] cleanup variants in TreeErrorKind Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/blob/tree.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/crates/core/src/blob/tree.rs b/crates/core/src/blob/tree.rs index 6dffa397..40abd23f 100644 --- a/crates/core/src/blob/tree.rs +++ b/crates/core/src/blob/tree.rs @@ -33,24 +33,12 @@ use crate::{ #[derive(thiserror::Error, Debug, displaydoc::Display)] #[non_exhaustive] pub enum TreeErrorKind { - /// blob `{0}` not found in index - BlobIdNotFound(TreeId), - /// `{0:?}` is not a directory - NotADirectory(OsString), - /// Path `{0:?}` not found - PathNotFound(OsString), /// path should not contain current or parent dir ContainsCurrentOrParentDirectory, /// `serde_json` couldn't serialize the tree: `{0:?}` SerializingTreeFailed(serde_json::Error), - /// `serde_json` couldn't deserialize tree from bytes of JSON text: `{0:?}` - DeserializingTreeFailed(serde_json::Error), /// slice is not UTF-8: `{0:?}` PathIsNotUtf8Conform(Utf8Error), - /// error in building nodestreamer: `{0:?}` - BuildingNodeStreamerFailed(ignore::Error), - /// failed to read file string from glob file: `{0:?}` - ReadingFileStringFromGlobsFailed(std::io::Error), /// Error `{kind}` in tree streamer: `{source}` Channel { kind: &'static str, From d0acebf0c6ab8e96ab411a02e4acaeacd686d06a Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 28 Oct 2024 22:28:52 +0100 Subject: [PATCH 096/129] cleanup variants in PackFileErrorKind Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/repofile/packfile.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/crates/core/src/repofile/packfile.rs b/crates/core/src/repofile/packfile.rs index 64564335..b0577295 100644 --- a/crates/core/src/repofile/packfile.rs +++ b/crates/core/src/repofile/packfile.rs @@ -20,14 +20,6 @@ pub enum PackFileErrorKind { ReadingBinaryRepresentationFailed(binrw::Error), /// Failed writing binary representation of the pack header: `{0:?}` WritingBinaryRepresentationFailed(binrw::Error), - /// Read header length is too large! Length: `{size_real}`, file size: `{pack_size}` - HeaderLengthTooLarge { size_real: u32, pack_size: u32 }, - /// decrypting from binary failed - BinaryDecryptionFailed, - /// Partial read of `PackFile` failed - PartialReadOfPackfileFailed, - /// writing Bytes failed - WritingBytesFailed, } pub(crate) type PackFileResult = Result; From 8d99678ea01f4dff18aafa078f9a53f4ead312dc Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 28 Oct 2024 23:08:37 +0100 Subject: [PATCH 097/129] AppendOnly errors Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/commands/config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/core/src/commands/config.rs b/crates/core/src/commands/config.rs index bf200b68..24adafab 100644 --- a/crates/core/src/commands/config.rs +++ b/crates/core/src/commands/config.rs @@ -42,8 +42,8 @@ pub(crate) fn apply_config( ) -> RusticResult { if repo.config().append_only == Some(true) { return Err(RusticError::new( - ErrorKind::Permission, - "Not allowed to change config on an append-only repository.", + ErrorKind::AppendOnly, + "Changing config is not allowed in append-only repositories. Please disable append-only mode first, if you know what you are doing. Aborting.", )); } From 9eaa796c10bda8937eb31f5681dd18625a846376 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 28 Oct 2024 23:10:57 +0100 Subject: [PATCH 098/129] cleanup ErrorKind Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/archiver.rs | 2 +- crates/core/src/error.rs | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/crates/core/src/archiver.rs b/crates/core/src/archiver.rs index 420f8d52..e9ab4ae3 100644 --- a/crates/core/src/archiver.rs +++ b/crates/core/src/archiver.rs @@ -221,7 +221,7 @@ impl<'a, BE: DecryptFullBackend, I: ReadGlobalIndex> Archiver<'a, BE, I> { summary.finalize(self.snap.time).map_err(|err| { RusticError::with_source( - ErrorKind::Processing, + ErrorKind::Internal, "Could not finalize summary, please check the logs for more information.", err, ) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 7fdd3022..2515d1b0 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -428,10 +428,10 @@ pub enum ErrorKind { /// Blob, Pack, Index or Tree Error // These are deep errors that are not expected to be handled by the user. Internal, - /// IO Error - Io, /// Invalid Input Error InvalidInput, + /// IO Error + Io, /// Key Error Key, /// Multithreading Error @@ -440,10 +440,6 @@ pub enum ErrorKind { Parsing, /// Password Error Password, - /// Permission Error - Permission, - /// Processing Error - Processing, /// Repository Error Repository, /// Something is not supported From edb0a9721d827d6c4dc718903e0c710867fdf9ac Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 28 Oct 2024 23:16:38 +0100 Subject: [PATCH 099/129] cleanup ErrorKind Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/commands/prune.rs | 4 ++-- crates/core/src/commands/restore.rs | 2 +- crates/core/src/error.rs | 2 -- crates/core/src/repofile/configfile.rs | 2 +- crates/core/src/repository/warm_up.rs | 4 ++-- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/crates/core/src/commands/prune.rs b/crates/core/src/commands/prune.rs index 0a34af3d..8edd17a3 100644 --- a/crates/core/src/commands/prune.rs +++ b/crates/core/src/commands/prune.rs @@ -699,8 +699,8 @@ impl PrunePlan { if version < 2 && opts.repack_uncompressed { return Err(RusticError::new( - ErrorKind::Config, - "Repository is version 1, cannot repack uncompressed packs. ", + ErrorKind::Unsupported, + "Repacking uncompressed pack is unsupported in Repository version 1.", ) .attach_context("config version", version.to_string())); } diff --git a/crates/core/src/commands/restore.rs b/crates/core/src/commands/restore.rs index c647d75a..47032d44 100644 --- a/crates/core/src/commands/restore.rs +++ b/crates/core/src/commands/restore.rs @@ -500,7 +500,7 @@ fn restore_contents( .build() .map_err(|err| { RusticError::with_source( - ErrorKind::Multithreading, + ErrorKind::Internal, "Failed to create the thread pool. Please try again.", err, ) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 2515d1b0..4ddfcf19 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -434,8 +434,6 @@ pub enum ErrorKind { Io, /// Key Error Key, - /// Multithreading Error - Multithreading, /// Parsing Error Parsing, /// Password Error diff --git a/crates/core/src/repofile/configfile.rs b/crates/core/src/repofile/configfile.rs index 77998c1c..daec3dd6 100644 --- a/crates/core/src/repofile/configfile.rs +++ b/crates/core/src/repofile/configfile.rs @@ -159,7 +159,7 @@ impl ConfigFile { (2, None) => Ok(Some(0)), // use default (=0) zstd compression (2, Some(c)) => Ok(Some(c)), _ => Err(RusticError::new( - ErrorKind::Config, + ErrorKind::Unsupported, "Config version not supported. Please make sure, that you use the correct version.", ) .attach_context("version", self.version.to_string())), diff --git a/crates/core/src/repository/warm_up.rs b/crates/core/src/repository/warm_up.rs index ff63529d..5621570b 100644 --- a/crates/core/src/repository/warm_up.rs +++ b/crates/core/src/repository/warm_up.rs @@ -134,8 +134,8 @@ fn warm_up_repo( .build() .map_err(|err| { RusticError::with_source( - ErrorKind::Multithreading, - "Failed to create thread pool for warm-up", + ErrorKind::Internal, + "Failed to create thread pool for warm-up. Please try again.", err, ) })?; From caae6bbf288345201dc98e5b1bbb246cd2a00831 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 28 Oct 2024 23:29:13 +0100 Subject: [PATCH 100/129] cleanup ErrorKind, remove parsing, and spread errors into InvalidInput, MissingInput, Internal, and Unsupported Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/local.rs | 2 +- crates/backend/src/opendal.rs | 32 +++++++++++++++--------- crates/backend/src/rclone.rs | 20 ++++++++------- crates/backend/src/rest.rs | 16 ++++++------ crates/backend/src/util.rs | 2 +- crates/core/src/commands/backup.rs | 2 +- crates/core/src/commands/check.rs | 6 ++--- crates/core/src/commands/forget.rs | 2 +- crates/core/src/commands/prune.rs | 10 +++++--- crates/core/src/error.rs | 6 ++--- crates/core/src/id.rs | 2 +- crates/core/src/repofile/configfile.rs | 2 +- crates/core/src/repofile/snapshotfile.rs | 2 +- 13 files changed, 59 insertions(+), 45 deletions(-) diff --git a/crates/backend/src/local.rs b/crates/backend/src/local.rs index cc3221e0..246e0898 100644 --- a/crates/backend/src/local.rs +++ b/crates/backend/src/local.rs @@ -139,7 +139,7 @@ impl LocalBackend { let command: CommandInput = actual_command.parse().map_err(|err| { RusticError::with_source( - ErrorKind::Parsing, + ErrorKind::Internal, "Failed to parse command input. This is a bug. Please report it.", err, ) diff --git a/crates/backend/src/opendal.rs b/crates/backend/src/opendal.rs index b21f3e2c..5bd21bfb 100644 --- a/crates/backend/src/opendal.rs +++ b/crates/backend/src/opendal.rs @@ -54,7 +54,7 @@ impl FromStr for Throttle { .map(|s| { ByteSize::from_str(s.trim()).map_err(|err| { RusticError::with_source( - ErrorKind::Parsing, + ErrorKind::InvalidInput, "Parsing ByteSize from throttle string failed", err, ) @@ -64,7 +64,7 @@ impl FromStr for Throttle { .map(|b| -> RusticResult { b?.as_u64().try_into().map_err(|err| { RusticError::with_source( - ErrorKind::Parsing, + ErrorKind::Internal, "Converting ByteSize to u32 failed", err, ) @@ -73,12 +73,12 @@ impl FromStr for Throttle { let bandwidth = values .next() .transpose()? - .ok_or_else(|| RusticError::new(ErrorKind::Parsing, "No bandwidth given."))?; + .ok_or_else(|| RusticError::new(ErrorKind::MissingInput, "No bandwidth given."))?; let burst = values .next() .transpose()? - .ok_or_else(|| RusticError::new(ErrorKind::Parsing, "No burst given."))?; + .ok_or_else(|| RusticError::new(ErrorKind::MissingInput, "No burst given."))?; Ok(Self { bandwidth, burst }) } @@ -104,8 +104,12 @@ impl OpenDALBackend { Some("false" | "off") => 0, None | Some("default") => constants::DEFAULT_RETRY, Some(value) => usize::from_str(value).map_err(|err| { - RusticError::with_source(ErrorKind::Parsing, "Parsing retry value failed", err) - .attach_context("value", value.to_string()) + RusticError::with_source( + ErrorKind::InvalidInput, + "Parsing retry value failed, the value must be a valid integer.", + err, + ) + .attach_context("value", value.to_string()) })?, }; let connections = options @@ -113,8 +117,8 @@ impl OpenDALBackend { .map(|c| { usize::from_str(c).map_err(|err| { RusticError::with_source( - ErrorKind::Parsing, - "Parsing connections value failed", + ErrorKind::InvalidInput, + "Parsing connections value failed, the value must be a valid integer.", err, ) .attach_context("value", c.to_string()) @@ -128,8 +132,12 @@ impl OpenDALBackend { .transpose()?; let schema = Scheme::from_str(path.as_ref()).map_err(|err| { - RusticError::with_source(ErrorKind::Parsing, "Parsing scheme from path failed", err) - .attach_context("path", path.as_ref().to_string()) + RusticError::with_source( + ErrorKind::InvalidInput, + "Parsing scheme from path failed, the path must contain a valid scheme.", + err, + ) + .attach_context("path", path.as_ref().to_string()) })?; let mut operator = Operator::via_iter(schema, options) .map_err(|err| { @@ -264,7 +272,7 @@ impl ReadBackend for OpenDALBackend { Id::default(), entry.content_length().try_into().map_err(|err| { RusticError::with_source( - ErrorKind::Parsing, + ErrorKind::Internal, "Parsing content length failed", err, ) @@ -309,7 +317,7 @@ impl ReadBackend for OpenDALBackend { .try_into() .map_err(|err| RusticError::with_source( - ErrorKind::Parsing, + ErrorKind::Internal, "Parsing content length failed", err, ) diff --git a/crates/backend/src/rclone.rs b/crates/backend/src/rclone.rs index 0f13d619..c4719bdb 100644 --- a/crates/backend/src/rclone.rs +++ b/crates/backend/src/rclone.rs @@ -68,8 +68,8 @@ fn check_clone_version(rclone_version_output: &[u8]) -> RusticResult<()> { let rclone_version = std::str::from_utf8(rclone_version_output) .map_err(|err| { RusticError::with_source( - ErrorKind::Parsing, - "Expected rclone version to be valid utf8, but it was not.", + ErrorKind::Internal, + "Expected rclone version to be valid utf8, but it was not. Please check the `rclone version` output manually.", err ) })? @@ -77,14 +77,16 @@ fn check_clone_version(rclone_version_output: &[u8]) -> RusticResult<()> { .next() .ok_or_else(|| { RusticError::new( - ErrorKind::Parsing, - "Expected rclone version to have at least one line, but it did not. Please check the rclone version output manually.", + ErrorKind::Internal, + "Expected rclone version to have at least one line, but it did not. Please check the `rclone version` output manually.", ) })? .trim_start_matches(|c: char| !c.is_numeric()); let mut parsed_version = Version::parse(rclone_version).map_err(|err| { - RusticError::with_source(ErrorKind::Parsing, "Error parsing rclone version.", err) + RusticError::with_source(ErrorKind::Internal, + "Error parsing rclone version. This should not happen. Please check the `rclone version` output manually.", + err) .attach_context("version", rclone_version) })?; @@ -102,7 +104,7 @@ fn check_clone_version(rclone_version_output: &[u8]) -> RusticResult<()> { if VersionReq::parse("<1.52.2") .map_err(|err| { RusticError::with_source( - ErrorKind::Parsing, + ErrorKind::Internal, "Error parsing version requirement. This should not happen.", err, ) @@ -148,7 +150,7 @@ impl RcloneBackend { .get("use-password") .map(|v| v.parse().map_err(|err| RusticError::with_source( - ErrorKind::Parsing, + ErrorKind::InvalidInput, "Expected 'use-password' to be a boolean, but it was not. Please check the configuration file.", err ) @@ -162,7 +164,7 @@ impl RcloneBackend { .output() .map_err(|err| RusticError::with_source( ErrorKind::ExternalCommand, - "Experienced an error while running `rclone version` command. Please check if rclone is installed and in your PATH.", + "Experienced an error while running `rclone version` command. Please check if rclone is installed correctly and is in your PATH.", err ))? .stdout; @@ -181,7 +183,7 @@ impl RcloneBackend { rclone_command.push_str(url.as_ref()); let rclone_command: CommandInput = rclone_command.parse().map_err( |err| RusticError::with_source( - ErrorKind::Parsing, + ErrorKind::InvalidInput, "Expected rclone command to be valid, but it was not. Please check the configuration file.", err ) diff --git a/crates/backend/src/rest.rs b/crates/backend/src/rest.rs index cd6d7e41..b9357ca9 100644 --- a/crates/backend/src/rest.rs +++ b/crates/backend/src/rest.rs @@ -155,7 +155,7 @@ impl RestBackend { }; let url = Url::parse(&url).map_err(|err| { - RusticError::with_source(ErrorKind::Parsing, "URL parsing failed", err) + RusticError::with_source(ErrorKind::InvalidInput, "URL parsing failed", err) .attach_context("url", url) })?; @@ -179,8 +179,8 @@ impl RestBackend { "default" => constants::DEFAULT_RETRY, _ => usize::from_str(&value).map_err(|err| { RusticError::with_source( - ErrorKind::Parsing, - "Cannot parse value, value not supported for option retry.", + ErrorKind::InvalidInput, + "Cannot parse value, invalid value for option retry.", err, ) .attach_context("value", value) @@ -191,7 +191,7 @@ impl RestBackend { } else if option == "timeout" { let timeout = humantime::Duration::from_str(&value).map_err(|err| { RusticError::with_source( - ErrorKind::Parsing, + ErrorKind::InvalidInput, "Could not parse value as `humantime` duration.", err, ) @@ -293,7 +293,7 @@ impl ReadBackend for RestBackend { }; let url = self.url.join(&path).map_err(|err| { - RusticError::with_source(ErrorKind::Parsing, "Joining URL failed", err) + RusticError::with_source(ErrorKind::Internal, "Joining URL failed", err) .attach_context("url", self.url.as_str()) .attach_context("tpe", tpe.to_string()) .attach_context("tpe dir", tpe.dirname().to_string()) @@ -391,7 +391,7 @@ impl ReadBackend for RestBackend { let offset2 = offset + length - 1; let header_value = format!("bytes={offset}-{offset2}"); let url = self.url(tpe, id).map_err(|err| { - RusticError::with_source(ErrorKind::Parsing, "Joining URL failed", err) + RusticError::with_source(ErrorKind::Internal, "Joining URL failed", err) .attach_context("url", self.url.as_str()) .attach_context("tpe", tpe.to_string()) .attach_context("tpe dir", tpe.dirname().to_string()) @@ -429,7 +429,7 @@ fn construct_join_url_error( id: &Id, self_url: &Url, ) -> Box { - RusticError::with_source(ErrorKind::Parsing, "Joining URL failed", err) + RusticError::with_source(ErrorKind::Internal, "Joining URL failed", err) .attach_context("url", self_url.as_str()) .attach_context("tpe", tpe.to_string()) .attach_context("tpe dir", tpe.dirname().to_string()) @@ -444,7 +444,7 @@ impl WriteBackend for RestBackend { /// * If the backoff failed. fn create(&self) -> RusticResult<()> { let url = self.url.join("?create=true").map_err(|err| { - RusticError::with_source(ErrorKind::Parsing, "Joining URL failed", err) + RusticError::with_source(ErrorKind::Internal, "Joining URL failed", err) .attach_context("url", self.url.as_str()) .attach_context("join input", "?create=true") })?; diff --git a/crates/backend/src/util.rs b/crates/backend/src/util.rs index cd59856e..4e725a4d 100644 --- a/crates/backend/src/util.rs +++ b/crates/backend/src/util.rs @@ -60,7 +60,7 @@ pub fn location_to_type_and_path( Some((scheme, path)) => Ok(( SupportedBackend::try_from(scheme).map_err(|err| { RusticError::with_source( - ErrorKind::Parsing, + ErrorKind::Unsupported, "The backend type is not supported. Please check the given backend and try again.", err ) diff --git a/crates/core/src/commands/backup.rs b/crates/core/src/commands/backup.rs index 60969c3e..dd999189 100644 --- a/crates/core/src/commands/backup.rs +++ b/crates/core/src/commands/backup.rs @@ -227,7 +227,7 @@ pub(crate) fn backup( Ok(p.parse_dot() .map_err(|err| { RusticError::with_source( - ErrorKind::Parsing, + ErrorKind::InvalidInput, "Failed to parse dotted path.", err, ) diff --git a/crates/core/src/commands/check.rs b/crates/core/src/commands/check.rs index 3832fcc1..35c86b8d 100644 --- a/crates/core/src/commands/check.rs +++ b/crates/core/src/commands/check.rs @@ -148,7 +148,7 @@ impl FromStr for ReadSubsetOption { // try to read percentage let percentage = p.parse().map_err(|err| { RusticError::with_source( - ErrorKind::Parsing, + ErrorKind::InvalidInput, "Error parsing percentage for ReadSubset option. Did you forget the '%'?", err, ) @@ -161,7 +161,7 @@ impl FromStr for ReadSubsetOption { let subset = parse_n_m(now, n, m).map_err( |err| RusticError::with_source( - ErrorKind::Parsing, + ErrorKind::InvalidInput, "Error parsing n/m for ReadSubset option. Allowed values: 'all', 'x%', 'n/m' or a size.", err ) @@ -175,7 +175,7 @@ impl FromStr for ReadSubsetOption { let byte_size = ByteSize::from_str(s) .map_err(|err| { RusticError::with_source( - ErrorKind::Parsing, + ErrorKind::InvalidInput, "Error parsing size for ReadSubset option. Allowed values: 'all', 'x%', 'n/m' or a size.", err ) diff --git a/crates/core/src/commands/forget.rs b/crates/core/src/commands/forget.rs index b5fc0a83..b86a39b6 100644 --- a/crates/core/src/commands/forget.rs +++ b/crates/core/src/commands/forget.rs @@ -524,7 +524,7 @@ impl KeepOptions { ) -> RusticResult> { if !self.is_valid() { return Err(RusticError::new( - ErrorKind::InvalidInput, + ErrorKind::Io, "No valid keep options specified, please make sure to specify at least one keep-* option.", )); } diff --git a/crates/core/src/commands/prune.rs b/crates/core/src/commands/prune.rs index 8edd17a3..bbdd68f7 100644 --- a/crates/core/src/commands/prune.rs +++ b/crates/core/src/commands/prune.rs @@ -202,7 +202,7 @@ impl FromStr for LimitOption { _ = copy.pop(); copy.parse().map_err(|err| { RusticError::with_source( - ErrorKind::Parsing, + ErrorKind::InvalidInput, "Failed to parse percentage limit.", err, ) @@ -212,8 +212,12 @@ impl FromStr for LimitOption { 'd' if s == "unlimited" => Self::Unlimited, _ => { let byte_size = ByteSize::from_str(s).map_err(|err| { - RusticError::with_source(ErrorKind::Parsing, "Failed to parse size limit.", err) - .attach_context("limit", s) + RusticError::with_source( + ErrorKind::InvalidInput, + "Failed to parse size limit.", + err, + ) + .attach_context("limit", s) })?; Self::Size(byte_size) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 4ddfcf19..28772000 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -430,12 +430,12 @@ pub enum ErrorKind { Internal, /// Invalid Input Error InvalidInput, - /// IO Error + /// Input/Output Error Io, /// Key Error Key, - /// Parsing Error - Parsing, + /// Missing Input Error + MissingInput, /// Password Error Password, /// Repository Error diff --git a/crates/core/src/id.rs b/crates/core/src/id.rs index 1abc3d89..acd4a2b1 100644 --- a/crates/core/src/id.rs +++ b/crates/core/src/id.rs @@ -87,7 +87,7 @@ impl FromStr for Id { let mut id = Self::default(); hex::decode_to_slice(s, &mut id.0).map_err(|err| { RusticError::with_source( - ErrorKind::Parsing, + ErrorKind::InvalidInput, "Failed to decode hex string into Id. The value must be a valid hexadecimal string.", err ) diff --git a/crates/core/src/repofile/configfile.rs b/crates/core/src/repofile/configfile.rs index daec3dd6..3f157718 100644 --- a/crates/core/src/repofile/configfile.rs +++ b/crates/core/src/repofile/configfile.rs @@ -139,7 +139,7 @@ impl ConfigFile { pub fn poly(&self) -> RusticResult { let chunker_poly = u64::from_str_radix(&self.chunker_polynomial, 16) .map_err(|err| RusticError::with_source( - ErrorKind::Parsing, + ErrorKind::InvalidInput, "Parsing u64 from hex failed for polynomial, the value must be a valid hexadecimal string.", err) .attach_context("polynomial",self.chunker_polynomial.to_string())) diff --git a/crates/core/src/repofile/snapshotfile.rs b/crates/core/src/repofile/snapshotfile.rs index 9ae42721..b52ced9d 100644 --- a/crates/core/src/repofile/snapshotfile.rs +++ b/crates/core/src/repofile/snapshotfile.rs @@ -135,7 +135,7 @@ impl SnapshotOptions { pub fn add_tags(mut self, tag: &str) -> RusticResult { self.tags.push(StringList::from_str(tag).map_err(|err| { RusticError::with_source( - ErrorKind::Parsing, + ErrorKind::InvalidInput, "Failed to create string list from tag. The value must be a valid unicode string.", err, ) From 354f7b20ef5b983b09c8de592ce6dfbf5b941310 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 28 Oct 2024 23:35:26 +0100 Subject: [PATCH 101/129] move error kind up in module Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/commands/forget.rs | 4 +- crates/core/src/error.rs | 146 +++++++++++++++-------------- 2 files changed, 76 insertions(+), 74 deletions(-) diff --git a/crates/core/src/commands/forget.rs b/crates/core/src/commands/forget.rs index b86a39b6..a8b4c6f3 100644 --- a/crates/core/src/commands/forget.rs +++ b/crates/core/src/commands/forget.rs @@ -524,8 +524,8 @@ impl KeepOptions { ) -> RusticResult> { if !self.is_valid() { return Err(RusticError::new( - ErrorKind::Io, - "No valid keep options specified, please make sure to specify at least one keep-* option.", + ErrorKind::InvalidInput, + "Invalid keep options specified, please make sure to specify at least one keep-* option.", )); } diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 28772000..0cfb53d3 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -64,6 +64,80 @@ pub(crate) mod constants { /// Result type that is being returned from methods that can fail and thus have [`RusticError`]s. pub type RusticResult> = Result; +/// Severity of an error, ranging from informational to fatal. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Severity { + /// Informational + Info, + + /// Warning + Warning, + + /// Error + Error, + + /// Fatal + Fatal, +} + +/// Status of an error, indicating whether it is permanent, temporary, or persistent. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Status { + /// Permanent, may not be retried + Permanent, + + /// Temporary, may be retried + Temporary, + + /// Persistent, may be retried, but may not succeed + Persistent, +} + +/// [`ErrorKind`] describes the errors that can happen while executing a high-level command. +/// +/// This is a non-exhaustive enum, so additional variants may be added in future. It is +/// recommended to match against the wildcard `_` instead of listing all possible variants, +/// to avoid problems when new variants are added. +#[non_exhaustive] +#[derive(thiserror::Error, Debug, displaydoc::Display)] +pub enum ErrorKind { + /// Append-only Error + AppendOnly, + /// Backend Error + Backend, + /// Command Error + Command, + /// Config Error + Config, + /// Cryptography Error + Cryptography, + /// External Command Error + ExternalCommand, + /// Internal Error + // Blob, Pack, Index, Tree Errors + // Compression, Parsing, Multithreading etc. + // These are deep errors that are not expected to be handled by the user. + Internal, + /// Invalid Input Error + InvalidInput, + /// Input/Output Error + Io, + /// Key Error + Key, + /// Missing Input Error + MissingInput, + /// Password Error + Password, + /// Repository Error + Repository, + /// Unsupported Feature Error + Unsupported, + /// Verification Error + Verification, + /// Virtual File System Error + Vfs, +} + #[derive(thiserror::Error, Debug)] #[non_exhaustive] /// Errors that can result from rustic. @@ -375,75 +449,3 @@ impl RusticError { }) } } - -/// Severity of an error, ranging from informational to fatal. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Severity { - /// Informational - Info, - - /// Warning - Warning, - - /// Error - Error, - - /// Fatal - Fatal, -} - -/// Status of an error, indicating whether it is permanent, temporary, or persistent. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Status { - /// Permanent, may not be retried - Permanent, - - /// Temporary, may be retried - Temporary, - - /// Persistent, may be retried, but may not succeed - Persistent, -} - -/// [`ErrorKind`] describes the errors that can happen while executing a high-level command. -/// -/// This is a non-exhaustive enum, so additional variants may be added in future. It is -/// recommended to match against the wildcard `_` instead of listing all possible variants, -/// to avoid problems when new variants are added. -#[non_exhaustive] -#[derive(thiserror::Error, Debug, displaydoc::Display)] -pub enum ErrorKind { - /// Append-only mode is enabled - AppendOnly, - /// Backend Error - Backend, - /// Command Error - Command, - /// Config Error - Config, - /// Crypto Error - Cryptography, - /// External Command Error - ExternalCommand, - /// Blob, Pack, Index or Tree Error - // These are deep errors that are not expected to be handled by the user. - Internal, - /// Invalid Input Error - InvalidInput, - /// Input/Output Error - Io, - /// Key Error - Key, - /// Missing Input Error - MissingInput, - /// Password Error - Password, - /// Repository Error - Repository, - /// Something is not supported - Unsupported, - /// Verification Error - Verification, - /// Virtual File System Error - Vfs, -} From 389b15eaab6d77de7264f2ce35932b601568e0f3 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 28 Oct 2024 23:41:42 +0100 Subject: [PATCH 102/129] add back error Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/repository.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/core/src/repository.rs b/crates/core/src/repository.rs index deb1cc52..df669092 100644 --- a/crates/core/src/repository.rs +++ b/crates/core/src/repository.rs @@ -256,7 +256,7 @@ impl RepositoryOptions { /// /// # Errors /// -// TODO: Add errors +/// * If reading the password failed pub fn read_password_from_reader(file: &mut impl BufRead) -> RusticResult { let mut password = String::new(); _ = file.read_line(&mut password).map_err(|err| { From 23db926d32381d4c17eda35d62b27c2fc457f521 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 28 Oct 2024 23:43:13 +0100 Subject: [PATCH 103/129] more InvalidInput errors Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/repofile/snapshotfile.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/core/src/repofile/snapshotfile.rs b/crates/core/src/repofile/snapshotfile.rs index b52ced9d..bc8489cb 100644 --- a/crates/core/src/repofile/snapshotfile.rs +++ b/crates/core/src/repofile/snapshotfile.rs @@ -389,7 +389,7 @@ impl SnapshotFile { .to_str() .ok_or_else(|| { RusticError::new( - ErrorKind::Internal, + ErrorKind::InvalidInput, "Failed to convert hostname to string. The value must be a valid unicode string.", ) .attach_context("hostname", hostname.to_string_lossy().to_string()) @@ -404,7 +404,7 @@ impl SnapshotFile { (_, Some(duration)) => DeleteOption::After( time + Duration::from_std(*duration).map_err(|err| { RusticError::with_source( - ErrorKind::Internal, + ErrorKind::InvalidInput, "Failed to convert duration to std::time::Duration. Please make sure the value is a valid duration string.", err, ) @@ -441,7 +441,7 @@ impl SnapshotFile { if let Some(ref path) = opts.description_from { snap.description = Some(std::fs::read_to_string(path).map_err(|err| { RusticError::with_source( - ErrorKind::Io, + ErrorKind::InvalidInput, "Failed to read description file. Please make sure the file exists and is readable.", err, ) From 4f44079c901ebe7809d63d44b4e049d78f5bc2f3 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 28 Oct 2024 23:50:57 +0100 Subject: [PATCH 104/129] update snapshot file for error display test Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/tests/snapshots/errors__error_display.snap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/core/tests/snapshots/errors__error_display.snap b/crates/core/tests/snapshots/errors__error_display.snap index 9269936b..1d8417e6 100644 --- a/crates/core/tests/snapshots/errors__error_display.snap +++ b/crates/core/tests/snapshots/errors__error_display.snap @@ -1,8 +1,8 @@ --- source: crates/core/tests/errors.rs -expression: TEST_ERROR.to_string() +expression: error --- -IO Error occurred in `rustic_core` +Input/Output Error occurred in `rustic_core` Message: A file could not be read, make sure the file is existing and readable by the system. From b484c37e6f54f2aabb46429fe6f095646bb8db31 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Tue, 29 Oct 2024 01:51:12 +0100 Subject: [PATCH 105/129] log errors before throwing them away Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/local.rs | 14 ++++++++++++-- crates/backend/src/opendal.rs | 4 ++-- crates/core/src/backend/cache.rs | 7 ++++++- crates/core/src/commands/restore.rs | 8 +++++++- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/crates/backend/src/local.rs b/crates/backend/src/local.rs index 246e0898..1a6be7e5 100644 --- a/crates/backend/src/local.rs +++ b/crates/backend/src/local.rs @@ -7,7 +7,7 @@ use std::{ use aho_corasick::AhoCorasick; use bytes::Bytes; -use log::{debug, trace, warn}; +use log::{debug, error, trace, warn}; use walkdir::WalkDir; use rustic_core::{ @@ -203,6 +203,11 @@ impl ReadBackend for LocalBackend { let walker = WalkDir::new(self.path.join(tpe.dirname())) .into_iter() + .inspect(|r| { + if let Err(err) = r { + error!("Error while listing files: {err:?}"); + } + }) .filter_map(walkdir::Result::ok) .filter(|e| e.file_type().is_file()) .filter_map(|e| e.file_name().to_string_lossy().parse::().ok()); @@ -255,6 +260,11 @@ impl ReadBackend for LocalBackend { let walker = WalkDir::new(path) .into_iter() + .inspect(|r| { + if let Err(err) = r { + error!("Error while listing files: {err:?}"); + } + }) .filter_map(walkdir::Result::ok) .filter(|e| e.file_type().is_file()) .map(|e| -> RusticResult<_> { @@ -284,7 +294,7 @@ impl ReadBackend for LocalBackend { }) .inspect(|r| { if let Err(err) = r { - warn!("Error while listing files: {:?}", err); + error!("Error while listing files: {err:?}"); } }) .filter_map(RusticResult::ok); diff --git a/crates/backend/src/opendal.rs b/crates/backend/src/opendal.rs index 5bd21bfb..f4025dc0 100644 --- a/crates/backend/src/opendal.rs +++ b/crates/backend/src/opendal.rs @@ -3,7 +3,7 @@ use std::{collections::HashMap, str::FromStr, sync::OnceLock}; use bytes::Bytes; use bytesize::ByteSize; -use log::{trace, warn}; +use log::{error, trace}; use opendal::{ layers::{BlockingLayer, ConcurrentLimitLayer, LoggingLayer, RetryLayer, ThrottleLayer}, BlockingOperator, Metakey, Operator, Scheme, @@ -327,7 +327,7 @@ impl ReadBackend for OpenDALBackend { }) .inspect(|r| { if let Err(err) = r { - warn!("Error while listing files: {:?}", err); + error!("Error while listing files: {err:?}"); } }) .filter_map(RusticResult::ok) diff --git a/crates/core/src/backend/cache.rs b/crates/core/src/backend/cache.rs index 0fc246ca..431b8309 100644 --- a/crates/core/src/backend/cache.rs +++ b/crates/core/src/backend/cache.rs @@ -8,7 +8,7 @@ use std::{ use bytes::Bytes; use dirs::cache_dir; -use log::{trace, warn}; +use log::{error, trace, warn}; use walkdir::WalkDir; use crate::{ @@ -323,6 +323,11 @@ impl Cache { let walker = WalkDir::new(path) .into_iter() + .inspect(|r| { + if let Err(err) = r { + error!("Error while listing files: {err:?}"); + } + }) .filter_map(walkdir::Result::ok) .filter(|e| { // only use files with length of 64 which are valid hex diff --git a/crates/core/src/commands/restore.rs b/crates/core/src/commands/restore.rs index 47032d44..0143f453 100644 --- a/crates/core/src/commands/restore.rs +++ b/crates/core/src/commands/restore.rs @@ -270,7 +270,13 @@ pub(crate) fn collect_and_prepare( .ignore(false) .sort_by_file_path(Path::cmp) .build() - .filter_map(Result::ok); // TODO: print out the ignored error + .inspect(|r| { + if let Err(err) = r { + error!("Error during collection of files: {err:?}"); + } + }) + .filter_map(Result::ok); + let mut next_dst = dst_iter.next(); let mut next_node = node_streamer.next().transpose()?; From f4babc31c3ebfa11800bb6b965799cb457728620 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Tue, 29 Oct 2024 02:06:01 +0100 Subject: [PATCH 106/129] adapt visibility to make `rustic-rs` build Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/backend/local_destination.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/core/src/backend/local_destination.rs b/crates/core/src/backend/local_destination.rs index b89d036f..fa984c3a 100644 --- a/crates/core/src/backend/local_destination.rs +++ b/crates/core/src/backend/local_destination.rs @@ -754,7 +754,7 @@ impl LocalDestination { /// /// If a file exists and size matches, this returns a `File` open for reading. /// In all other cases, returns `None` - pub(crate) fn get_matching_file(&self, item: impl AsRef, size: u64) -> Option { + pub fn get_matching_file(&self, item: impl AsRef, size: u64) -> Option { let filename = self.path(item); fs::symlink_metadata(&filename).map_or_else( |_| None, From a7c83601f22f1a72c3e38be44c626b0f4288ecdf Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Tue, 29 Oct 2024 04:12:00 +0100 Subject: [PATCH 107/129] update dep Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e54d61d6..33f39203 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ aho-corasick = "1.1.3" anyhow = "1.0.89" bytes = "1.7.2" enum-map = "2.7.3" -rustic_backend = { path = "crates/backend" } +rustic_backend = { path = "crates/backend", version = "0" } rustic_core = { path = "crates/core", version = "0" } rustic_testing = { path = "crates/testing", version = "0" } simplelog = "0.12.2" From b6a838640cd7e1e207f9942d935e5487fc82e58b Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Wed, 30 Oct 2024 01:04:48 +0100 Subject: [PATCH 108/129] fix local after review Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/local.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/crates/backend/src/local.rs b/crates/backend/src/local.rs index 1a6be7e5..04faa79b 100644 --- a/crates/backend/src/local.rs +++ b/crates/backend/src/local.rs @@ -36,15 +36,9 @@ impl LocalBackend { /// /// # Errors /// - // TODO: Add error types - /// - /// # Returns - /// - /// A new [`LocalBackend`] instance - /// - /// # Notes + /// * If the directory could not be created. /// - /// The following options are supported: + /// # Options /// /// * `post-create-command` - The command to call after a file was created. /// * `post-delete-command` - The command to call after a file was deleted. @@ -162,6 +156,7 @@ impl LocalBackend { if !status.success() { return Err( RusticError::new(ErrorKind::Command, "Command was not successful.") + .attach_context("command", command.to_string()) .attach_context("file_name", replace_with[0]) .attach_context("file_type", replace_with[1]) .attach_context("id", replace_with[2]) @@ -203,6 +198,7 @@ impl ReadBackend for LocalBackend { let walker = WalkDir::new(self.path.join(tpe.dirname())) .into_iter() + // TODO: What to do with errors? .inspect(|r| { if let Err(err) = r { error!("Error while listing files: {err:?}"); From c8ed8df926918630f11380d72b1d45d233f33ef8 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Wed, 30 Oct 2024 01:19:15 +0100 Subject: [PATCH 109/129] .ask_report() Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/local.rs | 35 ++-- crates/backend/src/opendal.rs | 10 +- crates/core/src/archiver/file_archiver.rs | 5 +- crates/core/src/archiver/tree_archiver.rs | 14 +- crates/core/src/backend/decrypt.rs | 30 +-- crates/core/src/backend/ignore.rs | 120 +++++------ crates/core/src/blob/packer.rs | 12 +- crates/core/src/blob/tree.rs | 209 +++++++++---------- crates/core/src/chunker.rs | 5 +- crates/core/src/commands/check.rs | 20 +- crates/core/src/commands/prune.rs | 31 +-- crates/core/src/commands/repair/snapshots.rs | 7 +- crates/core/src/error.rs | 37 +++- crates/core/src/repofile/keyfile.rs | 3 +- crates/core/src/repository.rs | 3 +- crates/core/src/vfs.rs | 84 ++++---- 16 files changed, 315 insertions(+), 310 deletions(-) diff --git a/crates/backend/src/local.rs b/crates/backend/src/local.rs index 04faa79b..a5147e70 100644 --- a/crates/backend/src/local.rs +++ b/crates/backend/src/local.rs @@ -121,8 +121,10 @@ impl LocalBackend { let ac = AhoCorasick::new(patterns).map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Experienced an error building AhoCorasick automaton for command replacement. This is a bug. Please report it.", - err) + "Experienced an error building AhoCorasick automaton for command replacement.", + err, + ) + .ask_report() })?; let replace_with = &[filename.to_str().unwrap(), tpe.dirname(), id.as_str()]; @@ -132,13 +134,10 @@ impl LocalBackend { debug!("calling {actual_command}..."); let command: CommandInput = actual_command.parse().map_err(|err| { - RusticError::with_source( - ErrorKind::Internal, - "Failed to parse command input. This is a bug. Please report it.", - err, - ) - .attach_context("command", actual_command) - .attach_context("replacement", replace_with.join(", ")) + RusticError::with_source(ErrorKind::Internal, "Failed to parse command input.", err) + .attach_context("command", actual_command) + .attach_context("replacement", replace_with.join(", ")) + .ask_report() })?; let status = Command::new(command.command()) @@ -243,10 +242,11 @@ impl ReadBackend for LocalBackend { .map_err(|err| RusticError::with_source( ErrorKind::Backend, - "Failed to convert file length to u32. This is a bug. Please report it.", + "Failed to convert file length to u32.", err ) .attach_context("length", path.metadata().unwrap().len().to_string()) + .ask_report() )?, )] } else { @@ -281,10 +281,11 @@ impl ReadBackend for LocalBackend { .map_err(|err| RusticError::with_source( ErrorKind::Backend, - "Failed to convert file length to u32. This is a bug. Please report it.", + "Failed to convert file length to u32.", err ) .attach_context("length", e.metadata().unwrap().len().to_string()) + .ask_report() )?, )) }) @@ -371,10 +372,11 @@ impl ReadBackend for LocalBackend { length.try_into().map_err(|err| { RusticError::with_source( ErrorKind::Backend, - "Failed to convert length to u64. This is a bug. Please report it.", + "Failed to convert length to u64.", err, ) .attach_context("length", length.to_string()) + .ask_report() })? ]; @@ -476,12 +478,9 @@ impl WriteBackend for LocalBackend { })?; file.set_len(buf.len().try_into().map_err(|err| { - RusticError::with_source( - ErrorKind::Internal, - "Failed to convert length to u64. This is a bug. Please report it.", - err, - ) - .attach_context("length", buf.len().to_string()) + RusticError::with_source(ErrorKind::Internal, "Failed to convert length to u64.", err) + .attach_context("length", buf.len().to_string()) + .ask_report() })?) .map_err(|err| { RusticError::with_source( diff --git a/crates/backend/src/opendal.rs b/crates/backend/src/opendal.rs index f4025dc0..a0b8a333 100644 --- a/crates/backend/src/opendal.rs +++ b/crates/backend/src/opendal.rs @@ -163,11 +163,8 @@ impl OpenDALBackend { let operator = operator .layer(LoggingLayer::default()) .layer(BlockingLayer::create().map_err(|err| { - RusticError::with_source( - ErrorKind::Backend, - "Creating BlockingLayer failed. This is a bug. Please report it.", - err, - ) + RusticError::with_source(ErrorKind::Backend, "Creating BlockingLayer failed.", err) + .ask_report() })?) .blocking(); @@ -225,9 +222,10 @@ impl ReadBackend for OpenDALBackend { if self.operator.is_exist("config").map_err(|err| { RusticError::with_source( ErrorKind::Backend, - "Path `config` does not exist. This is a bug. Please report it.", + "Path `config` does not exist.", err, ) + .ask_report() })? { vec![Id::default()] } else { diff --git a/crates/core/src/archiver/file_archiver.rs b/crates/core/src/archiver/file_archiver.rs index 4fe48792..f757113a 100644 --- a/crates/core/src/archiver/file_archiver.rs +++ b/crates/core/src/archiver/file_archiver.rs @@ -118,9 +118,10 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> FileArchiver<'a, BE, I> { .ok_or_else( || RusticError::new( ErrorKind::Internal, - "Failed to unpack tree type optional. Option should contain a value, but contained `None`. This is a bug. Please report it.", + "Failed to unpack tree type optional. Option should contain a value, but contained `None`.", ) - .attach_context("path", path.display().to_string()), + .attach_context("path", path.display().to_string()) + .ask_report(), )? .open() .map_err(|err| { diff --git a/crates/core/src/archiver/tree_archiver.rs b/crates/core/src/archiver/tree_archiver.rs index 297af3d5..278f02b1 100644 --- a/crates/core/src/archiver/tree_archiver.rs +++ b/crates/core/src/archiver/tree_archiver.rs @@ -103,10 +103,7 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> TreeArchiver<'a, BE, I> { } TreeType::EndTree => { let (path, mut node, parent, tree) = self.stack.pop().ok_or_else(|| { - RusticError::new( - ErrorKind::Internal, - "Tree stack is empty. This is a bug. Please report it.", - ) + RusticError::new(ErrorKind::Internal, "Tree stack is empty.").ask_report() })?; // save tree @@ -169,12 +166,9 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> TreeArchiver<'a, BE, I> { /// The id of the tree. fn backup_tree(&mut self, path: &Path, parent: &ParentResult) -> RusticResult { let (chunk, id) = self.tree.serialize().map_err(|err| { - RusticError::with_source( - ErrorKind::Internal, - "Failed to serialize tree. This is a bug. Please report it.", - err, - ) - .attach_context("path", path.to_string_lossy()) + RusticError::with_source(ErrorKind::Internal, "Failed to serialize tree.", err) + .attach_context("path", path.to_string_lossy()) + .ask_report() })?; let dirsize = chunk.len() as u64; let dirsize_bytes = ByteSize(dirsize).to_string_as(true); diff --git a/crates/core/src/backend/decrypt.rs b/crates/core/src/backend/decrypt.rs index 785d8292..c6df00ab 100644 --- a/crates/core/src/backend/decrypt.rs +++ b/crates/core/src/backend/decrypt.rs @@ -84,10 +84,11 @@ pub trait DecryptReadBackend: ReadBackend + Clone + 'static { if data.len() != length.get() as usize { return Err(RusticError::new( ErrorKind::Internal, - "Length of uncompressed data does not match the given length. This is likely a bug. Please report this issue.", + "Length of uncompressed data does not match the given length.", ) .attach_context("expected_length", length.get().to_string()) - .attach_context("actual_length", data.len().to_string())); + .attach_context("actual_length", data.len().to_string()) + .ask_report()); } } Ok(data.into()) @@ -254,9 +255,10 @@ pub trait DecryptWriteBackend: WriteBackend + Clone + 'static { let data = serde_json::to_vec(file).map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to serialize file to JSON. This is likely a bug. Please report this issue.", + "Failed to serialize file to JSON.", err, ) + .ask_report() })?; self.hash_write_full(F::TYPE, &data) @@ -279,9 +281,10 @@ pub trait DecryptWriteBackend: WriteBackend + Clone + 'static { let data = serde_json::to_vec(file).map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to serialize file to JSON. This is likely a bug. Please report this issue.", + "Failed to serialize file to JSON.", err, ) + .ask_report() })?; self.hash_write_full_uncompressed(F::TYPE, &data) @@ -447,16 +450,15 @@ impl DecryptBackend { /// encrypt and potentially compress some data fn encrypt_data(&self, data: &[u8]) -> RusticResult<(Vec, u32, Option)> { - let data_len: u32 = data - .len() - .try_into() - .map_err(|err| - RusticError::with_source( - ErrorKind::Internal, - "Failed to convert data length to u32. This is likely a bug. Please report this issue.", - err, - ).attach_context("length", data.len().to_string()) - )?; + let data_len: u32 = data.len().try_into().map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to convert data length to u32.", + err, + ) + .attach_context("length", data.len().to_string()) + .ask_report() + })?; let (data_encrypted, uncompressed_length) = match self.zstd { None => (self.key.encrypt_data(data)?, None), diff --git a/crates/core/src/backend/ignore.rs b/crates/core/src/backend/ignore.rs index 61a0f371..16f8e717 100644 --- a/crates/core/src/backend/ignore.rs +++ b/crates/core/src/backend/ignore.rs @@ -189,16 +189,15 @@ impl LocalSource { // FIXME: Refactor this to a function to be reused // This is the same of `tree::NodeStreamer::new_with_glob()` for g in &filter_opts.globs { - _ = override_builder - .add(g) - .map_err(|err| - RusticError::with_source( - ErrorKind::Internal, - "Failed to add glob pattern to override builder. This is a bug. Please report it.", - err, - ) - .attach_context("glob", g.to_string()) - )?; + _ = override_builder.add(g).map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to add glob pattern to override builder.", + err, + ) + .attach_context("glob", g.to_string()) + .ask_report() + })?; } for file in &filter_opts.glob_files { @@ -206,46 +205,44 @@ impl LocalSource { .map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to read string from glob file. This is a bug. Please report it.", + "Failed to read string from glob file.", err, ) .attach_context("glob file", file.to_string()) + .ask_report() })? .lines() { - _ = override_builder - .add(line) - .map_err(|err| - RusticError::with_source( - ErrorKind::Internal, - "Failed to add glob pattern line to override builder. This is a bug. Please report it.", - err, - ) - .attach_context("glob pattern line", line.to_string()) - )?; + _ = override_builder.add(line).map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to add glob pattern line to override builder.", + err, + ) + .attach_context("glob pattern line", line.to_string()) + .ask_report() + })?; } } - _ = override_builder - .case_insensitive(true) - .map_err(|err| + _ = override_builder.case_insensitive(true).map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to set case insensitivity in override builder.", + err, + ) + .ask_report() + })?; + for g in &filter_opts.iglobs { + _ = override_builder.add(g).map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to set case insensitivity in override builder. This is a bug. Please report it.", + "Failed to add iglob pattern to override builder.", err, ) - )?; - for g in &filter_opts.iglobs { - _ = override_builder - .add(g) - .map_err(|err| - RusticError::with_source( - ErrorKind::Internal, - "Failed to add iglob pattern to override builder. This is a bug. Please report it.", - err, - ) - .attach_context("iglob", g.to_string()) - )?; + .attach_context("iglob", g.to_string()) + .ask_report() + })?; } for file in &filter_opts.iglob_files { @@ -253,23 +250,23 @@ impl LocalSource { .map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to read string from iglob file. This is a bug. Please report it.", + "Failed to read string from iglob file.", err, ) .attach_context("iglob file", file.to_string()) + .ask_report() })? .lines() { - _ = override_builder - .add(line) - .map_err(|err| - RusticError::with_source( - ErrorKind::Internal, - "Failed to add iglob pattern line to override builder. This is a bug. Please report it.", - err, - ) - .attach_context("iglob pattern line", line.to_string()) - )?; + _ = override_builder.add(line).map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to add iglob pattern line to override builder.", + err, + ) + .attach_context("iglob pattern line", line.to_string()) + .ask_report() + })?; } } @@ -286,17 +283,14 @@ impl LocalSource { .sort_by_file_path(Path::cmp) .same_file_system(filter_opts.one_file_system) .max_filesize(filter_opts.exclude_larger_than.map(|s| s.as_u64())) - .overrides( - override_builder - .build() - .map_err(|err| + .overrides(override_builder.build().map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to build matcher for a set of glob overrides. This is a bug. Please report it.", + "Failed to build matcher for a set of glob overrides.", err, ) - )?, - ); + .ask_report() + })?); let exclude_if_present = filter_opts.exclude_if_present.clone(); if !filter_opts.exclude_if_present.is_empty() { @@ -408,23 +402,25 @@ impl Iterator for LocalSourceWalker { } .map(|e| { map_entry( - e.map_err(|err| + e.map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to get next entry from walk iterator. This is a bug. Please report it.", + "Failed to get next entry from walk iterator.", err, - ) - )?, + ) + .ask_report() + })?, self.save_opts.with_atime, self.save_opts.ignore_devid, ) - .map_err(|err| + .map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to map Directory entry to ReadSourceEntry. This is a bug. Please report it.", + "Failed to map Directory entry to ReadSourceEntry.", err, ) - ) + .ask_report() + }) }) } } diff --git a/crates/core/src/blob/packer.rs b/crates/core/src/blob/packer.rs index a058cf7a..4295434d 100644 --- a/crates/core/src/blob/packer.rs +++ b/crates/core/src/blob/packer.rs @@ -301,12 +301,9 @@ impl Packer { pub fn add(&self, data: Bytes, id: BlobId) -> RusticResult<()> { // compute size limit based on total size and size bounds self.add_with_sizelimit(data, id, None).map_err(|err| { - RusticError::with_source( - ErrorKind::Internal, - "Failed to add blob to packfile. This is a bug. Please report this issue and upload the log file.", - err - ) - .attach_context("blob id", id.to_string()) + RusticError::with_source(ErrorKind::Internal, "Failed to add blob to packfile.", err) + .attach_context("blob id", id.to_string()) + .ask_report() }) } @@ -514,9 +511,10 @@ impl RawPacker { self.save().map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to save packfile. Data may be lost. Please report this issue and upload the log file.", + "Failed to save packfile. Data may be lost.", err, ) + .ask_report() })?; self.file_writer.take().unwrap().finalize()?; diff --git a/crates/core/src/blob/tree.rs b/crates/core/src/blob/tree.rs index 40abd23f..211e721c 100644 --- a/crates/core/src/blob/tree.rs +++ b/crates/core/src/blob/tree.rs @@ -142,9 +142,10 @@ impl Tree { let tree = serde_json::from_slice(&data).map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to deserialize tree from JSON. This is a bug. Please report it.", + "Failed to deserialize tree from JSON.", err, ) + .ask_report() })?; Ok(tree) @@ -176,32 +177,27 @@ impl Tree { if let Some(p) = comp_to_osstr(p).map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to convert Path component to OsString. This is a bug. Please report it.", + "Failed to convert Path component to OsString.", err, ) .attach_context("path", path.display().to_string()) + .ask_report() })? { - let id = node - .subtree - .ok_or_else(|| - RusticError::new( - ErrorKind::Internal, - "Node is not a directory. This is a bug. Please report it.", - ).attach_context("node", p.to_string_lossy().to_string()) - ) - ?; + let id = node.subtree.ok_or_else(|| { + RusticError::new(ErrorKind::Internal, "Node is not a directory.") + .attach_context("node", p.to_string_lossy()) + .ask_report() + })?; let tree = Self::from_backend(be, index, id)?; node = tree .nodes .into_iter() .find(|node| node.name() == p) - .ok_or_else(|| - RusticError::new( - ErrorKind::Internal, - "Node not found in tree. This is a bug. Please report it.", - ).attach_context("node", p.to_string_lossy().to_string()) - ) - ?; + .ok_or_else(|| { + RusticError::new(ErrorKind::Internal, "Node not found in tree.") + .attach_context("node", p.to_string_lossy()) + .ask_report() + })?; } } @@ -240,11 +236,9 @@ impl Tree { Some(*node_idx) } else { let id = node.subtree.ok_or_else(|| { - RusticError::new( - ErrorKind::Internal, - "Subtree ID not found. This is a bug. Please report it.", - ) - .attach_context("node", path_comp[idx].to_string_lossy().to_string()) + RusticError::new(ErrorKind::Internal, "Subtree ID not found.") + .attach_context("node", path_comp[idx].to_string_lossy()) + .ask_report() })?; find_node_from_component( @@ -268,13 +262,15 @@ impl Tree { .components() .filter_map(|p| comp_to_osstr(p).transpose()) .collect::>() - .map_err(|err| + .map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to convert Path component to OsString. This is a bug. Please report it.", + "Failed to convert Path component to OsString.", err, - ).attach_context("path", path.display().to_string()) - )?; + ) + .attach_context("path", path.display().to_string()) + .ask_report() + })?; // caching all results let mut results_cache = vec![BTreeMap::new(); path_comp.len()]; @@ -347,11 +343,9 @@ impl Tree { let node_path = path.join(node.name()); if node.is_dir() { let id = node.subtree.ok_or_else(|| { - RusticError::new( - ErrorKind::Internal, - "Subtree ID not found. This is a bug. Please report it.", - ) - .attach_context("node", node.name().to_string_lossy().to_string()) + RusticError::new(ErrorKind::Internal, "Subtree ID not found.") + .attach_context("node", node.name().to_string_lossy()) + .ask_report() })?; result.append(&mut find_matching_nodes_recursive( @@ -594,16 +588,15 @@ where // FIXME: Refactor this to a function to be reused // This is the same of `backend::ignore::Localsource::new` for g in &opts.glob { - _ = override_builder - .add(g) - .map_err(|err| - RusticError::with_source( - ErrorKind::Internal, - "Failed to add glob pattern to override builder. This is a bug. Please report it.", - err, - ) - .attach_context("glob", g.to_string()) - )?; + _ = override_builder.add(g).map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to add glob pattern to override builder.", + err, + ) + .attach_context("glob", g.to_string()) + .ask_report() + })?; } for file in &opts.glob_file { @@ -611,46 +604,44 @@ where .map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to read string from glob file. This is a bug. Please report it.", + "Failed to read string from glob file.", err, ) .attach_context("glob file", file.to_string()) + .ask_report() })? .lines() { - _ = override_builder - .add(line) - .map_err(|err| - RusticError::with_source( - ErrorKind::Internal, - "Failed to add glob pattern line to override builder. This is a bug. Please report it.", - err, - ) - .attach_context("glob pattern line", line.to_string()) - )?; + _ = override_builder.add(line).map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to add glob pattern line to override builder.", + err, + ) + .attach_context("glob pattern line", line.to_string()) + .ask_report() + })?; } } - _ = override_builder - .case_insensitive(true) - .map_err(|err| + _ = override_builder.case_insensitive(true).map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to set case insensitivity in override builder.", + err, + ) + .ask_report() + })?; + for g in &opts.iglob { + _ = override_builder.add(g).map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to set case insensitivity in override builder. This is a bug. Please report it.", + "Failed to add iglob pattern to override builder.", err, ) - )?; - for g in &opts.iglob { - _ = override_builder - .add(g) - .map_err(|err| - RusticError::with_source( - ErrorKind::Internal, - "Failed to add iglob pattern to override builder. This is a bug. Please report it.", - err, - ) - .attach_context("iglob", g.to_string()) - )?; + .attach_context("iglob", g.to_string()) + .ask_report() + })?; } for file in &opts.iglob_file { @@ -658,34 +649,33 @@ where .map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to read string from iglob file. This is a bug. Please report it.", + "Failed to read string from iglob file.", err, ) .attach_context("iglob file", file.to_string()) + .ask_report() })? .lines() { - _ = override_builder - .add(line) - .map_err(|err| - RusticError::with_source( - ErrorKind::Internal, - "Failed to add iglob pattern line to override builder. This is a bug. Please report it.", - err, - ) - .attach_context("iglob pattern line", line.to_string()) - )?; + _ = override_builder.add(line).map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to add iglob pattern line to override builder.", + err, + ) + .attach_context("iglob pattern line", line.to_string()) + .ask_report() + })?; } } - let overrides = override_builder - .build() - .map_err(|err| - RusticError::with_source( - ErrorKind::Internal, - "Failed to build matcher for a set of glob overrides. This is a bug. Please report it.", - err, - ) - )?; + let overrides = override_builder.build().map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to build matcher for a set of glob overrides.", + err, + ) + .ask_report() + })?; Self::new_streamer(be, index, node, Some(overrides), opts.recursive) } @@ -816,11 +806,12 @@ impl TreeStreamerOnce

{ .map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to add tree ID to pending queue. This is a bug. Please report it.", + "Failed to add tree ID to pending queue.", err, ) .attach_context("tree id", id.to_string()) .attach_context("count", count.to_string()) + .ask_report() })? { streamer.p.inc(1); @@ -878,14 +869,13 @@ impl Iterator for TreeStreamerOnce

{ let (path, tree, count) = match self.queue_out.recv() { Ok(Ok(res)) => res, Err(err) => { - return Some(Err( - RusticError::with_source( - ErrorKind::Internal, - "Failed to receive tree from crossbeam channel. This is a bug. Please report it.", - err, - ) - .attach_context("finished ids", self.finished_ids.to_string()) - )); + return Some(Err(RusticError::with_source( + ErrorKind::Internal, + "Failed to receive tree from crossbeam channel.", + err, + ) + .attach_context("finished ids", self.finished_ids.to_string()) + .ask_report())); } Ok(Err(err)) => return Some(Err(err)), }; @@ -896,16 +886,19 @@ impl Iterator for TreeStreamerOnce

{ path.push(node.name()); match self.add_pending(path.clone(), id, count) { Ok(_) => {} - Err(err) => return Some(Err(err).map_err(|err| - RusticError::with_source( - ErrorKind::Internal, - "Failed to add tree ID to pending queue. This is a bug. Please report it.", - err, - ) - .attach_context("path", path.display().to_string()) - .attach_context("tree id", id.to_string()) - .attach_context("count", count.to_string()) - )), + Err(err) => { + return Some(Err(err).map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to add tree ID to pending queue.", + err, + ) + .attach_context("path", path.display().to_string()) + .attach_context("tree id", id.to_string()) + .attach_context("count", count.to_string()) + .ask_report() + })) + } } } } diff --git a/crates/core/src/chunker.rs b/crates/core/src/chunker.rs index 57b4149e..3df3697f 100644 --- a/crates/core/src/chunker.rs +++ b/crates/core/src/chunker.rs @@ -202,8 +202,9 @@ pub fn random_poly() -> RusticResult { Err(RusticError::new( ErrorKind::Internal, - "No suitable polynomial found, this should essentially never happen. Please try again, and then report this as a bug.", - )) + "No suitable polynomial found, this should essentially never happen. Please try again.", + ) + .ask_report()) } /// A trait for extending polynomials. diff --git a/crates/core/src/commands/check.rs b/crates/core/src/commands/check.rs index 35c86b8d..7d57d65f 100644 --- a/crates/core/src/commands/check.rs +++ b/crates/core/src/commands/check.rs @@ -689,13 +689,10 @@ fn check_pack( let header_len = PackHeaderRef::from_index_pack(&index_pack).size(); let pack_header_len = PackHeaderLength::from_binary(&data.split_off(data.len() - 4)) .map_err(|err| { - RusticError::with_source( - ErrorKind::Command, - "Error reading pack header length. This is a bug. Please report this error.", - err, - ) - .attach_context("pack id", id.to_string()) - .attach_context("header length", header_len.to_string()) + RusticError::with_source(ErrorKind::Command, "Error reading pack header length.", err) + .attach_context("pack id", id.to_string()) + .attach_context("header length", header_len.to_string()) + .ask_report() })? .to_u32(); if pack_header_len != header_len { @@ -708,12 +705,9 @@ fn check_pack( let pack_blobs = PackHeader::from_binary(&header) .map_err(|err| { - RusticError::with_source( - ErrorKind::Command, - "Error reading pack header. This is a bug. Please report this error.", - err, - ) - .attach_context("pack id", id.to_string()) + RusticError::with_source(ErrorKind::Command, "Error reading pack header.", err) + .attach_context("pack id", id.to_string()) + .ask_report() })? .into_blobs(); let mut blobs = index_pack.blobs; diff --git a/crates/core/src/commands/prune.rs b/crates/core/src/commands/prune.rs index bbdd68f7..a375d7f5 100644 --- a/crates/core/src/commands/prune.rs +++ b/crates/core/src/commands/prune.rs @@ -812,10 +812,12 @@ impl PrunePlan { fn check(&self) -> RusticResult<()> { for (id, count) in &self.used_ids { if *count == 0 { - return Err( - RusticError::new(ErrorKind::Command, "Blob is missing in index files, this should not happen. Please report this issue.") - .attach_context("blob id", id.to_string()), - ); + return Err(RusticError::new( + ErrorKind::Command, + "Blob is missing in index files.", + ) + .attach_context("blob id", id.to_string()) + .ask_report()); } } @@ -1083,16 +1085,15 @@ impl PrunePlan { Some(size) if size == pack.size => Ok(()), // size is ok => continue Some(size) => Err(RusticError::new( ErrorKind::Command, - "Pack size does not match the size in the index file. This should not happen. Please report this issue.", + "Pack size does not match the size in the index file.", ) .attach_context("pack id", pack.id.to_string()) .attach_context("size in index (expected)", pack.size.to_string()) .attach_context("size in pack (real)", size.to_string()) - ), - None => Err(RusticError::new( - ErrorKind::Command, - "Pack does not exist. This should not happen. Please report this issue.", - ).attach_context("pack id", pack.id.to_string())), + .ask_report()), + None => Err(RusticError::new(ErrorKind::Command, "Pack does not exist.") + .attach_context("pack id", pack.id.to_string()) + .ask_report()), } }; @@ -1100,9 +1101,10 @@ impl PrunePlan { PackToDo::Undecided => { return Err(RusticError::new( ErrorKind::Command, - "Pack got no decision what to do with it, please report this!", + "Pack got no decision what to do with it!", ) - .attach_context("pack id", pack.id.to_string())); + .attach_context("pack id", pack.id.to_string()) + .ask_report()); } PackToDo::Keep | PackToDo::Recover => { for blob in &pack.blobs { @@ -1352,9 +1354,10 @@ pub(crate) fn prune_repository( PackToDo::Undecided => { return Err(RusticError::new( ErrorKind::Command, - "Pack got no decision what to do with it, please report this!", + "Pack got no decision what to do with it!", ) - .attach_context("pack id", pack.id.to_string())); + .attach_context("pack id", pack.id.to_string()) + .ask_report()); } PackToDo::Keep => { // keep pack: add to new index diff --git a/crates/core/src/commands/repair/snapshots.rs b/crates/core/src/commands/repair/snapshots.rs index fd57864f..ebc43c87 100644 --- a/crates/core/src/commands/repair/snapshots.rs +++ b/crates/core/src/commands/repair/snapshots.rs @@ -284,11 +284,8 @@ pub(crate) fn repair_tree( (_, c) => { // the tree has been changed => save it let (chunk, new_id) = tree.serialize().map_err(|err| { - RusticError::with_source( - ErrorKind::Internal, - "Failed to serialize tree. This is likely a bug, please report it.", - err, - ) + RusticError::with_source(ErrorKind::Internal, "Failed to serialize tree.", err) + .ask_report() })?; if !index.has_tree(&new_id) && !dry_run { diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 0cfb53d3..d439c652 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -160,6 +160,9 @@ pub struct RusticError { /// Error code. error_code: Option, + /// Whether to ask the user to report the error. + ask_report: bool, + /// The URL of the issue tracker for opening a new issue. new_issue_url: Option, @@ -211,7 +214,18 @@ impl Display for RusticError { if let Some(code) = &self.error_code { let default_docs_url = SmolStr::from(constants::DEFAULT_DOCS_URL); - let docs_url = self.docs_url.as_ref().unwrap_or(&default_docs_url); + let docs_url = self + .docs_url + .as_ref() + .unwrap_or(&default_docs_url) + .to_string(); + + // If the docs_url doesn't end with a slash, add one. + let docs_url = if docs_url.ends_with('/') { + docs_url + } else { + docs_url + "/" + }; write!(f, "\n\nFor more information, see: {docs_url}{code}")?; } @@ -223,10 +237,12 @@ impl Display for RusticError { let default_issue_url = SmolStr::from(constants::DEFAULT_ISSUE_URL); let new_issue_url = self.new_issue_url.as_ref().unwrap_or(&default_issue_url); - write!( - f, - "\n\nIf you think this is an undiscovered bug, please open an issue at: {new_issue_url}" - )?; + if self.ask_report { + write!( + f, + "\n\nWe believe this is a bug, please report it by opening an issue at: {new_issue_url}\nIf you can, please attach an anonymized debug log to the issue.\n\nThank you for helping us improve rustic!" + )?; + } if let Some(backtrace) = &self.backtrace { write!(f, "\n\nBacktrace:\n{backtrace:?}")?; @@ -251,6 +267,7 @@ impl RusticError { existing_issue_url: None, severity: None, status: None, + ask_report: false, // `Backtrace::capture()` will check if backtrace has been enabled // internally. It's zero cost if backtrace is disabled. backtrace: Some(Backtrace::capture()), @@ -274,6 +291,7 @@ impl RusticError { existing_issue_url: None, severity: None, status: None, + ask_report: false, // `Backtrace::capture()` will check if backtrace has been enabled // internally. It's zero cost if backtrace is disabled. backtrace: Some(Backtrace::capture()), @@ -308,6 +326,7 @@ impl RusticError { existing_issue_url: None, severity: None, status: None, + ask_report: false, // `Backtrace::capture()` will check if backtrace has been enabled // internally. It's zero cost if backtrace is disabled. backtrace: Some(Backtrace::capture()), @@ -332,6 +351,14 @@ impl RusticError { }) } + /// Ask the user to report the error. + pub fn ask_report(self) -> Box { + Box::new(Self { + ask_report: true, + ..self + }) + } + /// Attach a chain to the cause of the error. pub fn attach_source( self, diff --git a/crates/core/src/repofile/keyfile.rs b/crates/core/src/repofile/keyfile.rs index cb18c679..c9aabe0c 100644 --- a/crates/core/src/repofile/keyfile.rs +++ b/crates/core/src/repofile/keyfile.rs @@ -215,9 +215,10 @@ impl KeyFile { let json_byte_vec = serde_json::to_vec(&masterkey).map_err(|err| { RusticError::with_source( ErrorKind::Key, - "Could not serialize as JSON byte vector. This is a bug, please report it.", + "Could not serialize as JSON byte vector.", err, ) + .ask_report() })?; let data = key.encrypt_data(&json_byte_vec)?; diff --git a/crates/core/src/repository.rs b/crates/core/src/repository.rs index df669092..bf22c31d 100644 --- a/crates/core/src/repository.rs +++ b/crates/core/src/repository.rs @@ -1567,9 +1567,10 @@ impl Repository { let ie = self.index().get_id(T::TYPE, &blob_id).ok_or_else(|| { RusticError::new( ErrorKind::Internal, - "BlobID not found in index, but should be there. This is a bug. Please report it.", + "BlobID not found in index, but should be there.", ) .attach_context("blob id", blob_id.to_string()) + .ask_report() })?; Ok(ie) diff --git a/crates/core/src/vfs.rs b/crates/core/src/vfs.rs index 56d9e854..0d3f197a 100644 --- a/crates/core/src/vfs.rs +++ b/crates/core/src/vfs.rs @@ -268,25 +268,29 @@ impl Vfs { && last_tree == snap.tree { if let Some(name) = last_name { - tree.add_tree(path, VfsTree::Link(name.clone())).map_err(|err| { - RusticError::with_source( - ErrorKind::Vfs, - "Failed to add a link to root tree. This should not happen, please report this bug.", - err - ) - .attach_context("path", path.display().to_string()) - .attach_context("name", name.to_string_lossy()) - })?; + tree.add_tree(path, VfsTree::Link(name.clone())) + .map_err(|err| { + RusticError::with_source( + ErrorKind::Vfs, + "Failed to add a link to root tree.", + err, + ) + .attach_context("path", path.display().to_string()) + .attach_context("name", name.to_string_lossy()) + .ask_report() + })?; } } else { - tree.add_tree(path, VfsTree::RusticTree(snap.tree)).map_err(|err| { + tree.add_tree(path, VfsTree::RusticTree(snap.tree)) + .map_err(|err| { RusticError::with_source( ErrorKind::Vfs, - "Failed to add repository tree to root tree. This should not happen, please report this bug.", - err + "Failed to add repository tree to root tree.", + err, ) .attach_context("path", path.display().to_string()) .attach_context("tree id", snap.tree.to_string()) + .ask_report() })?; } } @@ -304,15 +308,16 @@ impl Vfs { path.push("latest"); tree.add_tree(&path, VfsTree::Link(target.clone())) .map_err(|err| { - RusticError::with_source( - ErrorKind::Vfs, - "Failed to link latest entries to root tree. This should not happen, please report this bug.", - err - ) - .attach_context("latest", "link") - .attach_context("path", path.display().to_string()) - .attach_context("target", target.to_string_lossy()) - })?; + RusticError::with_source( + ErrorKind::Vfs, + "Failed to link latest entries to root tree.", + err, + ) + .attach_context("latest", "link") + .attach_context("path", path.display().to_string()) + .attach_context("target", target.to_string_lossy()) + .ask_report() + })?; } } } @@ -322,15 +327,16 @@ impl Vfs { path.push("latest"); tree.add_tree(&path, VfsTree::RusticTree(subtree)) .map_err(|err| { - RusticError::with_source( - ErrorKind::Vfs, - "Failed to add latest subtree to root tree. This should not happen, please report this bug.", - err - ) - .attach_context("latest", "dir") - .attach_context("path", path.display().to_string()) - .attach_context("tree id", subtree.to_string()) - })?; + RusticError::with_source( + ErrorKind::Vfs, + "Failed to add latest subtree to root tree.", + err, + ) + .attach_context("latest", "dir") + .attach_context("path", path.display().to_string()) + .attach_context("tree id", subtree.to_string()) + .ask_report() + })?; } } } @@ -361,12 +367,9 @@ impl Vfs { ) -> RusticResult { let meta = Metadata::default(); match self.tree.get_path(path).map_err(|err| { - RusticError::with_source( - ErrorKind::Vfs, - "Failed to get tree at given path. This should not happen, please report this bug.", - err, - ) - .attach_context("path", path.display().to_string()) + RusticError::with_source(ErrorKind::Vfs, "Failed to get tree at given path.", err) + .attach_context("path", path.display().to_string()) + .ask_report() })? { VfsPath::RusticPath(tree_id, path) => Ok(repo.node_from_path(*tree_id, &path)?), VfsPath::VirtualTree(_) => { @@ -410,12 +413,9 @@ impl Vfs { path: &Path, ) -> RusticResult> { let result = match self.tree.get_path(path).map_err(|err| { - RusticError::with_source( - ErrorKind::Vfs, - "Failed to get tree at given path. This should not happen, please report this bug.", - err, - ) - .attach_context("path", path.display().to_string()) + RusticError::with_source(ErrorKind::Vfs, "Failed to get tree at given path.", err) + .attach_context("path", path.display().to_string()) + .ask_report() })? { VfsPath::RusticPath(tree_id, path) => { let node = repo.node_from_path(*tree_id, &path)?; From d6d42f3d56a44bbd2740982026af1a1916ea2366 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Wed, 30 Oct 2024 01:34:53 +0100 Subject: [PATCH 110/129] don't unwrap in error description Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/local.rs | 43 +++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/crates/backend/src/local.rs b/crates/backend/src/local.rs index a5147e70..6866fc8d 100644 --- a/crates/backend/src/local.rs +++ b/crates/backend/src/local.rs @@ -226,29 +226,26 @@ impl ReadBackend for LocalBackend { if tpe == FileType::Config { return Ok(if path.exists() { - vec![( - Id::default(), - path.metadata() - .map_err(|err| + vec![(Id::default(), { + let metadata = path.metadata().map_err(|err| RusticError::with_source( ErrorKind::Backend, "Failed to query metadata of the file. Please check the file and try again.", err ) .attach_context("path", path.to_string_lossy()) - )? - .len() - .try_into() - .map_err(|err| - RusticError::with_source( - ErrorKind::Backend, - "Failed to convert file length to u32.", - err - ) - .attach_context("length", path.metadata().unwrap().len().to_string()) - .ask_report() - )?, - )] + )?; + + metadata.len().try_into().map_err(|err| { + RusticError::with_source( + ErrorKind::Backend, + "Failed to convert file length to u32.", + err, + ) + .attach_context("length", metadata.len().to_string()) + .ask_report() + })? + })] } else { Vec::new() }); @@ -266,7 +263,8 @@ impl ReadBackend for LocalBackend { .map(|e| -> RusticResult<_> { Ok(( e.file_name().to_string_lossy().parse()?, - e.metadata() + { + let metadata = e.metadata() .map_err(|err| RusticError::with_source( ErrorKind::Backend, @@ -275,7 +273,9 @@ impl ReadBackend for LocalBackend { ) .attach_context("path", e.path().to_string_lossy()) ) - ? + ?; + + metadata .len() .try_into() .map_err(|err| @@ -284,9 +284,10 @@ impl ReadBackend for LocalBackend { "Failed to convert file length to u32.", err ) - .attach_context("length", e.metadata().unwrap().len().to_string()) + .attach_context("length", metadata.len().to_string()) .ask_report() - )?, + )? + }, )) }) .inspect(|r| { From be71df276453bf3fa27e4fe0bc49fa4fb56cb828 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Wed, 30 Oct 2024 01:35:14 +0100 Subject: [PATCH 111/129] reorder error to be aligned with display impl Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/error.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index d439c652..b32133e5 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -145,35 +145,35 @@ pub struct RusticError { /// The kind of the error. kind: ErrorKind, - /// Chain to the cause of the error. - source: Option>, - /// The error message with guidance. guidance: SmolStr, /// The context of the error. context: Cow<'static, [(&'static str, SmolStr)]>, + /// Chain to the cause of the error. + source: Option>, + + /// Severity of the error. + severity: Option, + + /// The status of the error. + status: Option, + /// The URL of the documentation for the error. docs_url: Option, /// Error code. error_code: Option, - /// Whether to ask the user to report the error. - ask_report: bool, - /// The URL of the issue tracker for opening a new issue. new_issue_url: Option, /// The URL of an already existing issue that is related to this error. existing_issue_url: Option, - /// Severity of the error. - severity: Option, - - /// The status of the error. - status: Option, + /// Whether to ask the user to report the error. + ask_report: bool, /// Backtrace of the error. /// From b8bd50d8c36ce51c969778679e40c4a38e3af8bf Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Wed, 30 Oct 2024 01:46:23 +0100 Subject: [PATCH 112/129] DRY constructors for RusticError Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/error.rs | 38 ++++---------------------------------- 1 file changed, 4 insertions(+), 34 deletions(-) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index b32133e5..da5f018f 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -277,25 +277,10 @@ impl RusticError { /// Creates a new error with the given kind and guidance. pub fn with_source( kind: ErrorKind, - guidance: impl Into, + guidance: impl Into, source: impl Into>, ) -> Box { - Box::new(Self { - kind, - guidance: guidance.into().into(), - context: Cow::default(), - source: Some(source.into()), - error_code: None, - docs_url: None, - new_issue_url: None, - existing_issue_url: None, - severity: None, - status: None, - ask_report: false, - // `Backtrace::capture()` will check if backtrace has been enabled - // internally. It's zero cost if backtrace is disabled. - backtrace: Some(Backtrace::capture()), - }) + Self::new(kind, guidance).attach_source(source) } /// Checks if the error has a specific error code. @@ -312,25 +297,10 @@ impl RusticError { /// Creates a new error from a given error. pub fn from( - error: T, kind: ErrorKind, + error: T, ) -> Box { - Box::new(Self { - kind, - guidance: error.to_string().into(), - context: Cow::default(), - source: Some(Box::new(error)), - error_code: None, - docs_url: None, - new_issue_url: None, - existing_issue_url: None, - severity: None, - status: None, - ask_report: false, - // `Backtrace::capture()` will check if backtrace has been enabled - // internally. It's zero cost if backtrace is disabled. - backtrace: Some(Backtrace::capture()), - }) + Self::with_source(kind, error.to_string(), error) } } From e5084126ae960f07679e3d3448bd3a4cdaffb05b Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Wed, 30 Oct 2024 02:01:31 +0100 Subject: [PATCH 113/129] update error snapshots Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/tests/errors.rs | 14 +++++------ .../tests/snapshots/errors__error_debug.snap | 25 ++++++++++--------- .../snapshots/errors__error_display.snap | 7 +++++- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/crates/core/tests/errors.rs b/crates/core/tests/errors.rs index eddea288..38bfe22f 100644 --- a/crates/core/tests/errors.rs +++ b/crates/core/tests/errors.rs @@ -4,19 +4,19 @@ use rustic_core::{ErrorKind, RusticError, Severity, Status}; #[fixture] fn error() -> Box { - RusticError::new( + RusticError::with_source( ErrorKind::Io, "A file could not be read, make sure the file is existing and readable by the system.", + std::io::Error::new(std::io::ErrorKind::Other, "networking error"), ) + .attach_context("path", "/path/to/file") + .attach_context("called", "used s3 backend") .attach_status(Status::Permanent) .attach_severity(Severity::Error) .attach_error_code("C001") - .attach_context("path", "/path/to/file") - .attach_context("called", "used s3 backend") - .attach_source(std::io::Error::new( - std::io::ErrorKind::Other, - "networking error", - )) + .append_guidance_line("Appended guidance line") + .prepend_guidance_line("Prepended guidance line") + .ask_report() } #[rstest] diff --git a/crates/core/tests/snapshots/errors__error_debug.snap b/crates/core/tests/snapshots/errors__error_debug.snap index 90b1718e..cf649a5f 100644 --- a/crates/core/tests/snapshots/errors__error_debug.snap +++ b/crates/core/tests/snapshots/errors__error_debug.snap @@ -4,13 +4,7 @@ expression: error --- RusticError { kind: Io, - source: Some( - Custom { - kind: Other, - error: "networking error", - }, - ), - guidance: "A file could not be read, make sure the file is existing and readable by the system.", + guidance: "Prepended guidance line\nA file could not be read, make sure the file is existing and readable by the system.\nAppended guidance line", context: [ ( "path", @@ -21,18 +15,25 @@ RusticError { "used s3 backend", ), ], - docs_url: None, - error_code: Some( - "C001", + source: Some( + Custom { + kind: Other, + error: "networking error", + }, ), - new_issue_url: None, - existing_issue_url: None, severity: Some( Error, ), status: Some( Permanent, ), + docs_url: None, + error_code: Some( + "C001", + ), + new_issue_url: None, + existing_issue_url: None, + ask_report: true, backtrace: Some( , ), diff --git a/crates/core/tests/snapshots/errors__error_display.snap b/crates/core/tests/snapshots/errors__error_display.snap index 1d8417e6..116a46ae 100644 --- a/crates/core/tests/snapshots/errors__error_display.snap +++ b/crates/core/tests/snapshots/errors__error_display.snap @@ -5,7 +5,9 @@ expression: error Input/Output Error occurred in `rustic_core` Message: +Prepended guidance line A file could not be read, make sure the file is existing and readable by the system. +Appended guidance line Context: path: /path/to/file, @@ -19,7 +21,10 @@ Status: Permanent For more information, see: https://rustic.cli.rs/docs/errors/C001 -If you think this is an undiscovered bug, please open an issue at: https://github.com/rustic-rs/rustic_core/issues/new +We believe this is a bug, please report it by opening an issue at: https://github.com/rustic-rs/rustic_core/issues/new +If you can, please attach an anonymized debug log to the issue. + +Thank you for helping us improve rustic! Backtrace: From 63e496b6f743acfa4f70cdc6771fbe3bed41a1d1 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Wed, 30 Oct 2024 02:03:19 +0100 Subject: [PATCH 114/129] allow clippy lint for too many lines (103/100) Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/vfs.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/core/src/vfs.rs b/crates/core/src/vfs.rs index 0d3f197a..cf7056a3 100644 --- a/crates/core/src/vfs.rs +++ b/crates/core/src/vfs.rs @@ -221,6 +221,7 @@ impl Vfs { /// /// * If the path is not a normal path /// * If the path is a directory in the repository + #[allow(clippy::too_many_lines)] pub fn from_snapshots( mut snapshots: Vec, path_template: &str, From 5d41598ee7e408f7a74f6a84a014174e72874fc0 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Wed, 30 Oct 2024 16:04:25 +0100 Subject: [PATCH 115/129] Make it possible to use multiple different issue urls for an error Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/error.rs | 69 +++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index da5f018f..8e104849 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -49,6 +49,7 @@ // use std::fmt; use ::std::convert::Into; +use derive_more::derive::Display; use smol_str::SmolStr; use std::{ backtrace::Backtrace, @@ -65,7 +66,7 @@ pub(crate) mod constants { pub type RusticResult> = Result; /// Severity of an error, ranging from informational to fatal. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Display)] pub enum Severity { /// Informational Info, @@ -81,7 +82,7 @@ pub enum Severity { } /// Status of an error, indicating whether it is permanent, temporary, or persistent. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Display)] pub enum Status { /// Permanent, may not be retried Permanent, @@ -170,7 +171,7 @@ pub struct RusticError { new_issue_url: Option, /// The URL of an already existing issue that is related to this error. - existing_issue_url: Option, + existing_issue_urls: Cow<'static, [SmolStr]>, /// Whether to ask the user to report the error. ask_report: bool, @@ -183,33 +184,31 @@ pub struct RusticError { impl Display for RusticError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{} occurred in `rustic_core`", self.kind)?; + writeln!(f, "{} occurred in `rustic_core`", self.kind)?; - write!(f, "\n\nMessage:\n{}", self.guidance)?; + writeln!(f, "\nMessage:")?; + writeln!(f, "{}", self.guidance)?; if !self.context.is_empty() { - write!(f, "\n\nContext:\n")?; - write!( - f, - "{}", - self.context - .iter() - .map(|(k, v)| format!("{k}: {v}")) - .collect::>() - .join(",\n") - )?; + writeln!(f, "\nContext:")?; + self.context + .iter() + .try_for_each(|(key, value)| writeln!(f, "- {key}: {value}"))?; } if let Some(cause) = &self.source { - write!(f, "\n\nCaused by: {cause}")?; + writeln!(f, "\nCaused by:")?; + writeln!(f, "{cause}")?; } if let Some(severity) = &self.severity { - write!(f, "\n\nSeverity: {severity:?}")?; + writeln!(f, "\nSeverity:")?; + writeln!(f, "{severity}")?; } if let Some(status) = &self.status { - write!(f, "\n\nStatus: {status:?}")?; + writeln!(f, "\nStatus:")?; + writeln!(f, "\n{status}")?; } if let Some(code) = &self.error_code { @@ -227,25 +226,35 @@ impl Display for RusticError { docs_url + "/" }; - write!(f, "\n\nFor more information, see: {docs_url}{code}")?; + writeln!(f, "\nFor more information, see: {docs_url}{code}")?; } - if let Some(existing_issue_url) = &self.existing_issue_url { - write!(f, "\n\nThis might be a related issue, please check it for a possible workaround and/or further guidance: {existing_issue_url}")?; + if !self.existing_issue_urls.is_empty() { + writeln!(f, "\nRelated issues:")?; + self.existing_issue_urls + .iter() + .try_for_each(|url| writeln!(f, "- {url}"))?; } let default_issue_url = SmolStr::from(constants::DEFAULT_ISSUE_URL); let new_issue_url = self.new_issue_url.as_ref().unwrap_or(&default_issue_url); if self.ask_report { - write!( + writeln!( + f, + "\nWe believe this is a bug, please report it by opening an issue at: {new_issue_url}" + )?; + writeln!( f, - "\n\nWe believe this is a bug, please report it by opening an issue at: {new_issue_url}\nIf you can, please attach an anonymized debug log to the issue.\n\nThank you for helping us improve rustic!" + "If you can, please attach an anonymized debug log to the issue. + Thank you for helping us improve rustic!" )?; + writeln!(f, "Thank you for helping us improve rustic!")?; } if let Some(backtrace) = &self.backtrace { - write!(f, "\n\nBacktrace:\n{backtrace:?}")?; + writeln!(f, "\nBacktrace:")?; + writeln!(f, "{backtrace}")?; } Ok(()) @@ -264,7 +273,7 @@ impl RusticError { error_code: None, docs_url: None, new_issue_url: None, - existing_issue_url: None, + existing_issue_urls: Cow::default(), severity: None, status: None, ask_report: false, @@ -413,11 +422,11 @@ impl RusticError { } /// Attach the URL of an already existing issue that is related to this error. - pub fn attach_existing_issue_url(self, value: impl Into) -> Box { - Box::new(Self { - existing_issue_url: Some(value.into()), - ..self - }) + pub fn attach_existing_issue_url(mut self, value: impl Into) -> Box { + let mut issue_urls = self.existing_issue_urls.into_owned(); + issue_urls.push(value.into()); + self.existing_issue_urls = Cow::from(issue_urls); + Box::new(self) } /// Attach the severity of the error. From 8e8abe1cccb214a7b695ba4df1417a84a6cb56f0 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Wed, 30 Oct 2024 18:02:15 +0100 Subject: [PATCH 116/129] update error kind and error display impl Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/local.rs | 14 +- crates/backend/src/rclone.rs | 4 +- crates/core/src/archiver/file_archiver.rs | 2 +- crates/core/src/backend/cache.rs | 46 +++--- crates/core/src/backend/ignore.rs | 2 +- crates/core/src/backend/local_destination.rs | 4 +- crates/core/src/chunker.rs | 4 +- crates/core/src/commands/dump.rs | 6 +- crates/core/src/commands/key.rs | 6 +- crates/core/src/commands/restore.rs | 8 +- crates/core/src/error.rs | 143 ++++++++++-------- crates/core/src/repository.rs | 6 +- crates/core/tests/errors.rs | 5 +- .../tests/snapshots/errors__error_debug.snap | 22 +-- .../snapshots/errors__error_display.snap | 35 +++-- 15 files changed, 176 insertions(+), 131 deletions(-) diff --git a/crates/backend/src/local.rs b/crates/backend/src/local.rs index 6866fc8d..f17e9dfc 100644 --- a/crates/backend/src/local.rs +++ b/crates/backend/src/local.rs @@ -405,7 +405,7 @@ impl WriteBackend for LocalBackend { trace!("creating repo at {:?}", self.path); fs::create_dir_all(&self.path).map_err(|err| { RusticError::with_source( - ErrorKind::Io, + ErrorKind::InputOutput, "Failed to create the directory. Please check the path and try again.", err, ) @@ -416,7 +416,7 @@ impl WriteBackend for LocalBackend { let path = self.path.join(tpe.dirname()); fs::create_dir_all(path.clone()).map_err(|err| { RusticError::with_source( - ErrorKind::Io, + ErrorKind::InputOutput, "Failed to create the directory. Please check the path and try again.", err, ) @@ -428,7 +428,7 @@ impl WriteBackend for LocalBackend { let path = self.path.join("data").join(hex::encode([i])); fs::create_dir_all(path.clone()).map_err(|err| { RusticError::with_source( - ErrorKind::Io, + ErrorKind::InputOutput, "Failed to create the directory. Please check the path and try again.", err, ) @@ -471,7 +471,7 @@ impl WriteBackend for LocalBackend { .open(&filename) .map_err(|err| { RusticError::with_source( - ErrorKind::Io, + ErrorKind::InputOutput, "Failed to open the file. Please check the file and try again.", err, ) @@ -485,7 +485,7 @@ impl WriteBackend for LocalBackend { })?) .map_err(|err| { RusticError::with_source( - ErrorKind::Io, + ErrorKind::InputOutput, "Failed to set the length of the file. Please check the file and try again.", err, ) @@ -494,7 +494,7 @@ impl WriteBackend for LocalBackend { file.write_all(&buf).map_err(|err| { RusticError::with_source( - ErrorKind::Io, + ErrorKind::InputOutput, "Failed to write to the buffer. Please check the file and try again.", err, ) @@ -503,7 +503,7 @@ impl WriteBackend for LocalBackend { file.sync_all().map_err(|err| { RusticError::with_source( - ErrorKind::Io, + ErrorKind::InputOutput, "Failed to sync OS Metadata to disk. Please check the file and try again.", err, ) diff --git a/crates/backend/src/rclone.rs b/crates/backend/src/rclone.rs index c4719bdb..60b97b7d 100644 --- a/crates/backend/src/rclone.rs +++ b/crates/backend/src/rclone.rs @@ -244,7 +244,7 @@ impl RcloneBackend { .read_line(&mut line) .map_err(|err| RusticError::with_source( - ErrorKind::Io, + ErrorKind::InputOutput, "Experienced an error while reading rclone output. Please check if rclone is installed and working correctly.", err ) @@ -269,7 +269,7 @@ impl RcloneBackend { if use_password { if !rest_url.starts_with("http://") { return Err(RusticError::new( - ErrorKind::Io, + ErrorKind::InputOutput, "Please make sure, the URL starts with 'http://'!", ) .attach_context("url", rest_url)); diff --git a/crates/core/src/archiver/file_archiver.rs b/crates/core/src/archiver/file_archiver.rs index f757113a..d0e7ba6b 100644 --- a/crates/core/src/archiver/file_archiver.rs +++ b/crates/core/src/archiver/file_archiver.rs @@ -126,7 +126,7 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> FileArchiver<'a, BE, I> { .open() .map_err(|err| { RusticError::with_source( - ErrorKind::Io, + ErrorKind::InputOutput, "Failed to open ReadSourceOpen", err, ) diff --git a/crates/core/src/backend/cache.rs b/crates/core/src/backend/cache.rs index 431b8309..4cfd4574 100644 --- a/crates/core/src/backend/cache.rs +++ b/crates/core/src/backend/cache.rs @@ -243,22 +243,30 @@ impl Cache { }; fs::create_dir_all(&path).map_err(|err| { - RusticError::with_source(ErrorKind::Io, "Failed to create cache directory", err) - .attach_context("path", path.display().to_string()) - .attach_context("id", id.to_string()) + RusticError::with_source( + ErrorKind::InputOutput, + "Failed to create cache directory", + err, + ) + .attach_context("path", path.display().to_string()) + .attach_context("id", id.to_string()) })?; cachedir::ensure_tag(&path).map_err(|err| { - RusticError::with_source(ErrorKind::Io, "Failed to ensure cache directory tag", err) - .attach_context("path", path.display().to_string()) - .attach_context("id", id.to_string()) + RusticError::with_source( + ErrorKind::InputOutput, + "Failed to ensure cache directory tag", + err, + ) + .attach_context("path", path.display().to_string()) + .attach_context("id", id.to_string()) })?; path.push(id.to_hex()); fs::create_dir_all(&path).map_err(|err| { RusticError::with_source( - ErrorKind::Io, + ErrorKind::InputOutput, "Failed to create cache directory with id", err, ) @@ -400,7 +408,7 @@ impl Cache { } Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None), Err(err) => Err(RusticError::with_source( - ErrorKind::Io, + ErrorKind::InputOutput, "Failed to read full data of file", err, ) @@ -440,18 +448,20 @@ impl Cache { Ok(file) => file, Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(None), Err(err) => { - return Err( - RusticError::with_source(ErrorKind::Io, "Failed to open file", err) - .attach_context("tpe", tpe.to_string()) - .attach_context("id", id.to_string()), + return Err(RusticError::with_source( + ErrorKind::InputOutput, + "Failed to open file", + err, ) + .attach_context("tpe", tpe.to_string()) + .attach_context("id", id.to_string())) } }; _ = file .seek(SeekFrom::Start(u64::from(offset))) .map_err(|err| { - RusticError::with_source(ErrorKind::Io, "Failed to seek in file", err) + RusticError::with_source(ErrorKind::InputOutput, "Failed to seek in file", err) .attach_context("tpe", tpe.to_string()) .attach_context("id", id.to_string()) .attach_context("offset", offset.to_string()) @@ -460,7 +470,7 @@ impl Cache { let mut vec = vec![0; length as usize]; file.read_exact(&mut vec).map_err(|err| { - RusticError::with_source(ErrorKind::Io, "Failed to read from file", err) + RusticError::with_source(ErrorKind::InputOutput, "Failed to read from file", err) .attach_context("tpe", tpe.to_string()) .attach_context("id", id.to_string()) .attach_context("offset", offset.to_string()) @@ -489,7 +499,7 @@ impl Cache { let dir = self.dir(tpe, id); fs::create_dir_all(&dir).map_err(|err| { - RusticError::with_source(ErrorKind::Io, "Failed to create directories", err) + RusticError::with_source(ErrorKind::InputOutput, "Failed to create directories", err) .attach_context("path", dir.display().to_string()) .attach_context("tpe", tpe.to_string()) .attach_context("id", id.to_string()) @@ -503,12 +513,12 @@ impl Cache { .write(true) .open(&filename) .map_err(|err| { - RusticError::with_source(ErrorKind::Io, "Failed to open file", err) + RusticError::with_source(ErrorKind::InputOutput, "Failed to open file", err) .attach_context("path", filename.display().to_string()) })?; file.write_all(buf).map_err(|err| { - RusticError::with_source(ErrorKind::Io, "Failed to write to buffer", err) + RusticError::with_source(ErrorKind::InputOutput, "Failed to write to buffer", err) .attach_context("path", filename.display().to_string()) .attach_context("tpe", tpe.to_string()) .attach_context("id", id.to_string()) @@ -531,7 +541,7 @@ impl Cache { trace!("cache writing tpe: {:?}, id: {}", &tpe, &id); let filename = self.path(tpe, id); fs::remove_file(&filename).map_err(|err| { - RusticError::with_source(ErrorKind::Io, "Failed to remove file", err) + RusticError::with_source(ErrorKind::InputOutput, "Failed to remove file", err) .attach_context("path", filename.display().to_string()) .attach_context("tpe", tpe.to_string()) .attach_context("id", id.to_string()) diff --git a/crates/core/src/backend/ignore.rs b/crates/core/src/backend/ignore.rs index 16f8e717..1d37cf75 100644 --- a/crates/core/src/backend/ignore.rs +++ b/crates/core/src/backend/ignore.rs @@ -333,7 +333,7 @@ impl ReadSourceOpen for OpenFile { let path = self.0; File::open(&path).map_err(|err| { RusticError::with_source( - ErrorKind::Io, + ErrorKind::InputOutput, "Failed to open file. Please make sure the file exists and is accessible.", err, ) diff --git a/crates/core/src/backend/local_destination.rs b/crates/core/src/backend/local_destination.rs index fa984c3a..837f7d55 100644 --- a/crates/core/src/backend/local_destination.rs +++ b/crates/core/src/backend/local_destination.rs @@ -153,7 +153,7 @@ impl LocalDestination { if let Some(path) = path.parent() { fs::create_dir_all(path).map_err(|err| { RusticError::with_source( - ErrorKind::Io, + ErrorKind::InputOutput, "The directory could not be created.", err, ) @@ -163,7 +163,7 @@ impl LocalDestination { } else { fs::create_dir_all(&path).map_err(|err| { RusticError::with_source( - ErrorKind::Io, + ErrorKind::InputOutput, "The directory could not be created.", err, ) diff --git a/crates/core/src/chunker.rs b/crates/core/src/chunker.rs index 3df3697f..a8260151 100644 --- a/crates/core/src/chunker.rs +++ b/crates/core/src/chunker.rs @@ -113,7 +113,7 @@ impl Iterator for ChunkIter { Ok(size) => size, Err(err) => { return Some(Err(RusticError::with_source( - ErrorKind::Io, + ErrorKind::InputOutput, "Failed to read from reader in iterator", err, ))); @@ -157,7 +157,7 @@ impl Iterator for ChunkIter { Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue, Err(err) => { return Some(Err(RusticError::with_source( - ErrorKind::Io, + ErrorKind::InputOutput, "Failed to read from reader in iterator", err, ))); diff --git a/crates/core/src/commands/dump.rs b/crates/core/src/commands/dump.rs index 4c48c91a..5725a2f5 100644 --- a/crates/core/src/commands/dump.rs +++ b/crates/core/src/commands/dump.rs @@ -39,7 +39,11 @@ pub(crate) fn dump( for id in node.content.as_ref().unwrap() { let data = repo.get_blob_cached(&BlobId::from(**id), BlobType::Data)?; w.write_all(&data).map_err(|err| { - RusticError::with_source(ErrorKind::Io, "Failed to write data to writer.", err) + RusticError::with_source( + ErrorKind::InputOutput, + "Failed to write data to writer.", + err, + ) })?; } Ok(()) diff --git a/crates/core/src/commands/key.rs b/crates/core/src/commands/key.rs index a057f1f5..76238b00 100644 --- a/crates/core/src/commands/key.rs +++ b/crates/core/src/commands/key.rs @@ -109,7 +109,11 @@ pub(crate) fn add_key_to_repo( let keyfile = KeyFile::generate(key, &pass, ko.hostname, ko.username, ko.with_created)?; let data = serde_json::to_vec(&keyfile).map_err(|err| { - RusticError::with_source(ErrorKind::Io, "Failed to serialize keyfile to JSON.", err) + RusticError::with_source( + ErrorKind::InputOutput, + "Failed to serialize keyfile to JSON.", + err, + ) })?; let id = KeyId::from(hash(&data)); diff --git a/crates/core/src/commands/restore.rs b/crates/core/src/commands/restore.rs index 0143f453..04b35f72 100644 --- a/crates/core/src/commands/restore.rs +++ b/crates/core/src/commands/restore.rs @@ -222,7 +222,7 @@ pub(crate) fn collect_and_prepare( dest.create_dir(path) .map_err(|err| { RusticError::with_source( - ErrorKind::Io, + ErrorKind::InputOutput, "Failed to create the directory. Please check the path and try again.", err ) @@ -453,7 +453,7 @@ fn restore_contents( let path = &filenames[i]; dest.set_length(path, *size).map_err(|err| { RusticError::with_source( - ErrorKind::Io, + ErrorKind::InputOutput, "Failed to set the length of the file. Please check the path and try again.", err, ) @@ -678,7 +678,7 @@ impl RestorePlan { .transpose() .map_err(|err| RusticError::with_source( - ErrorKind::Io, + ErrorKind::InputOutput, "Failed to get the metadata of the file. Please check the path and try again.", err ) @@ -699,7 +699,7 @@ impl RestorePlan { .transpose() .map_err(|err| RusticError::with_source( - ErrorKind::Io, + ErrorKind::InputOutput, "Failed to get the metadata of the file. Please check the path and try again.", err ) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 8e104849..ada6444a 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -48,12 +48,12 @@ // use std::error::Error as StdError; // use std::fmt; -use ::std::convert::Into; use derive_more::derive::Display; use smol_str::SmolStr; use std::{ backtrace::Backtrace, borrow::Cow, + convert::Into, fmt::{self, Display}, }; @@ -94,48 +94,56 @@ pub enum Status { Persistent, } +// NOTE: +// +// we use `an error related to {kind}` in the Display impl, so the variant display comments +// should be able to be used in a sentence. +// /// [`ErrorKind`] describes the errors that can happen while executing a high-level command. /// /// This is a non-exhaustive enum, so additional variants may be added in future. It is /// recommended to match against the wildcard `_` instead of listing all possible variants, /// to avoid problems when new variants are added. #[non_exhaustive] -#[derive(thiserror::Error, Debug, displaydoc::Display)] +#[derive(thiserror::Error, Debug, displaydoc::Display, Default, Clone, Copy, PartialEq, Eq)] pub enum ErrorKind { - /// Append-only Error + /// append-only mode AppendOnly, - /// Backend Error + /// the backend Backend, - /// Command Error + /// the a command Command, - /// Config Error - Config, - /// Cryptography Error + /// the configuration + Configuration, + /// cryptographic operations Cryptography, - /// External Command Error + /// running an external command ExternalCommand, - /// Internal Error + /// internal operations // Blob, Pack, Index, Tree Errors // Compression, Parsing, Multithreading etc. // These are deep errors that are not expected to be handled by the user. Internal, - /// Invalid Input Error + /// invalid user input InvalidInput, - /// Input/Output Error - Io, - /// Key Error + /// input/output operations + InputOutput, + /// a key Key, - /// Missing Input Error + /// missing user input MissingInput, - /// Password Error + /// general operations + #[default] + Other, + /// password handling Password, - /// Repository Error + /// the repository Repository, - /// Unsupported Feature Error + /// unsupported operations Unsupported, - /// Verification Error + /// verification Verification, - /// Virtual File System Error + /// the virtual filesystem Vfs, } @@ -149,6 +157,21 @@ pub struct RusticError { /// The error message with guidance. guidance: SmolStr, + /// The URL of the documentation for the error. + docs_url: Option, + + /// Error code. + error_code: Option, + + /// Whether to ask the user to report the error. + ask_report: bool, + + /// The URL of an already existing issue that is related to this error. + existing_issue_urls: Cow<'static, [SmolStr]>, + + /// The URL of the issue tracker for opening a new issue. + new_issue_url: Option, + /// The context of the error. context: Cow<'static, [(&'static str, SmolStr)]>, @@ -161,21 +184,6 @@ pub struct RusticError { /// The status of the error. status: Option, - /// The URL of the documentation for the error. - docs_url: Option, - - /// Error code. - error_code: Option, - - /// The URL of the issue tracker for opening a new issue. - new_issue_url: Option, - - /// The URL of an already existing issue that is related to this error. - existing_issue_urls: Cow<'static, [SmolStr]>, - - /// Whether to ask the user to report the error. - ask_report: bool, - /// Backtrace of the error. /// // Need to use option, otherwise thiserror will not be able to derive the Error trait. @@ -184,33 +192,16 @@ pub struct RusticError { impl Display for RusticError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "{} occurred in `rustic_core`", self.kind)?; + writeln!(f, "Well, this is embarrassing.")?; + writeln!( + f, + "\n`rustic_core` experienced an error related to `{}`.", + self.kind + )?; writeln!(f, "\nMessage:")?; writeln!(f, "{}", self.guidance)?; - if !self.context.is_empty() { - writeln!(f, "\nContext:")?; - self.context - .iter() - .try_for_each(|(key, value)| writeln!(f, "- {key}: {value}"))?; - } - - if let Some(cause) = &self.source { - writeln!(f, "\nCaused by:")?; - writeln!(f, "{cause}")?; - } - - if let Some(severity) = &self.severity { - writeln!(f, "\nSeverity:")?; - writeln!(f, "{severity}")?; - } - - if let Some(status) = &self.status { - writeln!(f, "\nStatus:")?; - writeln!(f, "\n{status}")?; - } - if let Some(code) = &self.error_code { let default_docs_url = SmolStr::from(constants::DEFAULT_DOCS_URL); let docs_url = self @@ -236,20 +227,42 @@ impl Display for RusticError { .try_for_each(|url| writeln!(f, "- {url}"))?; } - let default_issue_url = SmolStr::from(constants::DEFAULT_ISSUE_URL); - let new_issue_url = self.new_issue_url.as_ref().unwrap_or(&default_issue_url); - if self.ask_report { + let default_issue_url = SmolStr::from(constants::DEFAULT_ISSUE_URL); + let new_issue_url = self.new_issue_url.as_ref().unwrap_or(&default_issue_url); + writeln!( f, - "\nWe believe this is a bug, please report it by opening an issue at: {new_issue_url}" + "\nWe believe this is a bug, please report it by opening an issue at:" )?; + writeln!(f, "{new_issue_url}")?; writeln!( f, - "If you can, please attach an anonymized debug log to the issue. - Thank you for helping us improve rustic!" + "\nIf you can, please attach an anonymized debug log to the issue." )?; - writeln!(f, "Thank you for helping us improve rustic!")?; + writeln!(f, "\nThank you for helping us improve rustic!")?; + } + + writeln!(f, "\n\nSome additional details ...")?; + + if !self.context.is_empty() { + writeln!(f, "\nContext:")?; + self.context + .iter() + .try_for_each(|(key, value)| writeln!(f, "- {key}:\t{value}"))?; + } + + if let Some(cause) = &self.source { + writeln!(f, "\nCaused by:")?; + writeln!(f, "{cause} : (source: {:?})", cause.source())?; + } + + if let Some(severity) = &self.severity { + writeln!(f, "\nSeverity: {severity}")?; + } + + if let Some(status) = &self.status { + writeln!(f, "\nStatus: {status}")?; } if let Some(backtrace) = &self.backtrace { diff --git a/crates/core/src/repository.rs b/crates/core/src/repository.rs index bf22c31d..98545529 100644 --- a/crates/core/src/repository.rs +++ b/crates/core/src/repository.rs @@ -424,7 +424,7 @@ impl Repository { 1 => Ok(Some(ConfigId::from(config_ids[0]))), 0 => Ok(None), _ => Err(RusticError::new( - ErrorKind::Config, + ErrorKind::Configuration, "More than one repository found. Please check the config file.", ) .attach_context("name", self.name.clone())), @@ -483,7 +483,7 @@ impl Repository { pub fn open_with_password(self, password: &str) -> RusticResult> { let config_id = self.config_id()?.ok_or_else(|| { RusticError::new( - ErrorKind::Config, + ErrorKind::Configuration, "No repository config file found. Please check the repository.", ) .attach_context("name", self.name.clone()) @@ -576,7 +576,7 @@ impl Repository { ) -> RusticResult> { if self.config_id()?.is_some() { return Err(RusticError::new( - ErrorKind::Config, + ErrorKind::Configuration, "Config file already exists. Please check the repository.", ) .attach_context("name", self.name)); diff --git a/crates/core/tests/errors.rs b/crates/core/tests/errors.rs index 38bfe22f..5fb98a96 100644 --- a/crates/core/tests/errors.rs +++ b/crates/core/tests/errors.rs @@ -5,9 +5,9 @@ use rustic_core::{ErrorKind, RusticError, Severity, Status}; #[fixture] fn error() -> Box { RusticError::with_source( - ErrorKind::Io, + ErrorKind::InputOutput, "A file could not be read, make sure the file is existing and readable by the system.", - std::io::Error::new(std::io::ErrorKind::Other, "networking error"), + std::io::Error::new(std::io::ErrorKind::ConnectionReset, "Networking Error"), ) .attach_context("path", "/path/to/file") .attach_context("called", "used s3 backend") @@ -16,6 +16,7 @@ fn error() -> Box { .attach_error_code("C001") .append_guidance_line("Appended guidance line") .prepend_guidance_line("Prepended guidance line") + .attach_existing_issue_url("https://github.com/rustic-rs/rustic_core/issues/209") .ask_report() } diff --git a/crates/core/tests/snapshots/errors__error_debug.snap b/crates/core/tests/snapshots/errors__error_debug.snap index cf649a5f..edae1870 100644 --- a/crates/core/tests/snapshots/errors__error_debug.snap +++ b/crates/core/tests/snapshots/errors__error_debug.snap @@ -3,8 +3,17 @@ source: crates/core/tests/errors.rs expression: error --- RusticError { - kind: Io, + kind: InputOutput, guidance: "Prepended guidance line\nA file could not be read, make sure the file is existing and readable by the system.\nAppended guidance line", + docs_url: None, + error_code: Some( + "C001", + ), + ask_report: true, + existing_issue_urls: [ + "https://github.com/rustic-rs/rustic_core/issues/209", + ], + new_issue_url: None, context: [ ( "path", @@ -17,8 +26,8 @@ RusticError { ], source: Some( Custom { - kind: Other, - error: "networking error", + kind: ConnectionReset, + error: "Networking Error", }, ), severity: Some( @@ -27,13 +36,6 @@ RusticError { status: Some( Permanent, ), - docs_url: None, - error_code: Some( - "C001", - ), - new_issue_url: None, - existing_issue_url: None, - ask_report: true, backtrace: Some( , ), diff --git a/crates/core/tests/snapshots/errors__error_display.snap b/crates/core/tests/snapshots/errors__error_display.snap index 116a46ae..3f129cd6 100644 --- a/crates/core/tests/snapshots/errors__error_display.snap +++ b/crates/core/tests/snapshots/errors__error_display.snap @@ -2,29 +2,40 @@ source: crates/core/tests/errors.rs expression: error --- -Input/Output Error occurred in `rustic_core` +Well, this is embarrassing. + +`rustic_core` experienced an error related to `input/output operations`. Message: Prepended guidance line A file could not be read, make sure the file is existing and readable by the system. Appended guidance line -Context: -path: /path/to/file, -called: used s3 backend - -Caused by: networking error - -Severity: Error +For more information, see: https://rustic.cli.rs/docs/errors/C001 -Status: Permanent +Related issues: +- https://github.com/rustic-rs/rustic_core/issues/209 -For more information, see: https://rustic.cli.rs/docs/errors/C001 +We believe this is a bug, please report it by opening an issue at: +https://github.com/rustic-rs/rustic_core/issues/new -We believe this is a bug, please report it by opening an issue at: https://github.com/rustic-rs/rustic_core/issues/new If you can, please attach an anonymized debug log to the issue. Thank you for helping us improve rustic! + +Some additional details ... + +Context: +- path: /path/to/file +- called: used s3 backend + +Caused by: +Networking Error : (source: None) + +Severity: Error + +Status: Permanent + Backtrace: - +disabled backtrace From 8dab3881917d7b591247fa2c996449e9ba7755ae Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Wed, 30 Oct 2024 20:07:33 +0100 Subject: [PATCH 117/129] replace placeholders based on context keys in guidance Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/error.rs | 13 ++++++++++++- crates/core/tests/errors.rs | 2 +- .../core/tests/snapshots/errors__error_debug.snap | 2 +- .../core/tests/snapshots/errors__error_display.snap | 2 +- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index ada6444a..efb0d296 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -200,7 +200,18 @@ impl Display for RusticError { )?; writeln!(f, "\nMessage:")?; - writeln!(f, "{}", self.guidance)?; + if self.context.is_empty() { + writeln!(f, "{}", self.guidance)?; + } else { + // If there is context, we want to iterate over it + // use the key to replace the placeholder in the guidance. + let mut guidance = self.guidance.to_string(); + self.context.iter().for_each(|(key, value)| { + let pattern = "{".to_owned() + key + "}"; + guidance = guidance.replace(&pattern, value); + }); + writeln!(f, "{guidance}")?; + } if let Some(code) = &self.error_code { let default_docs_url = SmolStr::from(constants::DEFAULT_DOCS_URL); diff --git a/crates/core/tests/errors.rs b/crates/core/tests/errors.rs index 5fb98a96..2171dd81 100644 --- a/crates/core/tests/errors.rs +++ b/crates/core/tests/errors.rs @@ -6,7 +6,7 @@ use rustic_core::{ErrorKind, RusticError, Severity, Status}; fn error() -> Box { RusticError::with_source( ErrorKind::InputOutput, - "A file could not be read, make sure the file is existing and readable by the system.", + "A file could not be read, make sure the file at `{path}` is existing and readable by the system.", std::io::Error::new(std::io::ErrorKind::ConnectionReset, "Networking Error"), ) .attach_context("path", "/path/to/file") diff --git a/crates/core/tests/snapshots/errors__error_debug.snap b/crates/core/tests/snapshots/errors__error_debug.snap index edae1870..6868ff11 100644 --- a/crates/core/tests/snapshots/errors__error_debug.snap +++ b/crates/core/tests/snapshots/errors__error_debug.snap @@ -4,7 +4,7 @@ expression: error --- RusticError { kind: InputOutput, - guidance: "Prepended guidance line\nA file could not be read, make sure the file is existing and readable by the system.\nAppended guidance line", + guidance: "Prepended guidance line\nA file could not be read, make sure the file at `{path}` is existing and readable by the system.\nAppended guidance line", docs_url: None, error_code: Some( "C001", diff --git a/crates/core/tests/snapshots/errors__error_display.snap b/crates/core/tests/snapshots/errors__error_display.snap index 3f129cd6..598bd9eb 100644 --- a/crates/core/tests/snapshots/errors__error_display.snap +++ b/crates/core/tests/snapshots/errors__error_display.snap @@ -8,7 +8,7 @@ Well, this is embarrassing. Message: Prepended guidance line -A file could not be read, make sure the file is existing and readable by the system. +A file could not be read, make sure the file at `/path/to/file` is existing and readable by the system. Appended guidance line For more information, see: https://rustic.cli.rs/docs/errors/C001 From 72f17827cc7c10bfe05bee5d57c99567659f531e Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Wed, 30 Oct 2024 21:34:30 +0100 Subject: [PATCH 118/129] Use the context placeholders in the guidance messages Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/choose.rs | 2 +- crates/backend/src/local.rs | 73 +++++++++-------- crates/backend/src/opendal.rs | 47 ++++++----- crates/backend/src/rclone.rs | 16 ++-- crates/backend/src/rest.rs | 28 ++++--- crates/backend/src/util.rs | 2 +- crates/core/src/archiver/file_archiver.rs | 6 +- crates/core/src/archiver/tree_archiver.rs | 10 ++- crates/core/src/backend.rs | 11 ++- crates/core/src/backend/cache.rs | 86 +++++++++++++------- crates/core/src/backend/decrypt.rs | 9 +- crates/core/src/backend/ignore.rs | 24 +++--- crates/core/src/backend/local_destination.rs | 4 +- crates/core/src/blob/packer.rs | 42 +++++----- crates/core/src/blob/tree.rs | 63 ++++++++------ crates/core/src/commands/backup.rs | 33 +++++--- crates/core/src/commands/cat.rs | 2 +- crates/core/src/commands/check.rs | 28 ++++--- crates/core/src/commands/config.rs | 24 +++--- crates/core/src/commands/dump.rs | 4 +- crates/core/src/commands/merge.rs | 12 ++- crates/core/src/commands/prune.rs | 36 ++++---- crates/core/src/commands/repair/index.rs | 14 +--- crates/core/src/commands/restore.rs | 14 ++-- crates/core/src/id.rs | 2 +- crates/core/src/index.rs | 9 +- crates/core/src/repofile/configfile.rs | 4 +- crates/core/src/repofile/keyfile.rs | 4 +- crates/core/src/repofile/packfile.rs | 23 +++--- crates/core/src/repofile/snapshotfile.rs | 8 +- crates/core/src/repository.rs | 26 +++--- crates/core/src/repository/warm_up.rs | 2 +- crates/core/src/vfs.rs | 38 +++++---- crates/testing/src/backend.rs | 12 ++- 34 files changed, 406 insertions(+), 312 deletions(-) diff --git a/crates/backend/src/choose.rs b/crates/backend/src/choose.rs index ad8d1009..1eee995f 100644 --- a/crates/backend/src/choose.rs +++ b/crates/backend/src/choose.rs @@ -119,7 +119,7 @@ impl BackendOptions { .map_err(|err| { RusticError::with_source( ErrorKind::Backend, - "Could not load the backend. Please check the given backend and try again.", + "Could not load the backend `{name}` at `{location}`. Please check the given backend and try again.", err, ) .attach_context("name", be_type.to_string()) diff --git a/crates/backend/src/local.rs b/crates/backend/src/local.rs index f17e9dfc..82685915 100644 --- a/crates/backend/src/local.rs +++ b/crates/backend/src/local.rs @@ -134,10 +134,14 @@ impl LocalBackend { debug!("calling {actual_command}..."); let command: CommandInput = actual_command.parse().map_err(|err| { - RusticError::with_source(ErrorKind::Internal, "Failed to parse command input.", err) - .attach_context("command", actual_command) - .attach_context("replacement", replace_with.join(", ")) - .ask_report() + RusticError::with_source( + ErrorKind::Internal, + "Failed to parse command input: `{command}` is not a valid command.", + err, + ) + .attach_context("command", actual_command) + .attach_context("replacement", replace_with.join(", ")) + .ask_report() })?; let status = Command::new(command.command()) @@ -146,21 +150,22 @@ impl LocalBackend { .map_err(|err| { RusticError::with_source( ErrorKind::Command, - "Failed to execute command. Please check the command and try again.", + "Failed to execute `{command}`. Please check the command and try again.", err, ) .attach_context("command", command.to_string()) })?; if !status.success() { - return Err( - RusticError::new(ErrorKind::Command, "Command was not successful.") - .attach_context("command", command.to_string()) - .attach_context("file_name", replace_with[0]) - .attach_context("file_type", replace_with[1]) - .attach_context("id", replace_with[2]) - .attach_context("status", status.to_string()), - ); + return Err(RusticError::new( + ErrorKind::Command, + "Command was not successful: `{command}` failed with status `{status}`.", + ) + .attach_context("command", command.to_string()) + .attach_context("file_name", replace_with[0]) + .attach_context("file_type", replace_with[1]) + .attach_context("id", replace_with[2]) + .attach_context("status", status.to_string())); } Ok(()) } @@ -230,7 +235,7 @@ impl ReadBackend for LocalBackend { let metadata = path.metadata().map_err(|err| RusticError::with_source( ErrorKind::Backend, - "Failed to query metadata of the file. Please check the file and try again.", + "Failed to query metadata of the file `{path}`. Please check the file and try again.", err ) .attach_context("path", path.to_string_lossy()) @@ -239,7 +244,7 @@ impl ReadBackend for LocalBackend { metadata.len().try_into().map_err(|err| { RusticError::with_source( ErrorKind::Backend, - "Failed to convert file length to u32.", + "Failed to convert file length `{length}` to u32.", err, ) .attach_context("length", metadata.len().to_string()) @@ -268,7 +273,7 @@ impl ReadBackend for LocalBackend { .map_err(|err| RusticError::with_source( ErrorKind::Backend, - "Failed to query metadata of the file. Please check the file and try again.", + "Failed to query metadata of the file `{path}`. Please check the file and try again.", err ) .attach_context("path", e.path().to_string_lossy()) @@ -281,7 +286,7 @@ impl ReadBackend for LocalBackend { .map_err(|err| RusticError::with_source( ErrorKind::Backend, - "Failed to convert file length to u32.", + "Failed to convert file length `{length}` to u32.", err ) .attach_context("length", metadata.len().to_string()) @@ -353,7 +358,7 @@ impl ReadBackend for LocalBackend { let mut file = File::open(self.path(tpe, id)).map_err(|err| { RusticError::with_source( ErrorKind::Backend, - "Failed to open the file. Please check the file and try again.", + "Failed to open the file `{path}`. Please check the file and try again.", err, ) .attach_context("path", self.path(tpe, id).to_string_lossy()) @@ -361,7 +366,7 @@ impl ReadBackend for LocalBackend { _ = file.seek(SeekFrom::Start(offset.into())).map_err(|err| { RusticError::with_source( ErrorKind::Backend, - "Failed to seek to the position in the file. Please check the file and try again.", + "Failed to seek to the position `{offset}` in the file `{path}`. Please check the file and try again.", err, ) .attach_context("path", self.path(tpe, id).to_string_lossy()) @@ -373,7 +378,7 @@ impl ReadBackend for LocalBackend { length.try_into().map_err(|err| { RusticError::with_source( ErrorKind::Backend, - "Failed to convert length to u64.", + "Failed to convert length `{length}` to u64.", err, ) .attach_context("length", length.to_string()) @@ -384,7 +389,7 @@ impl ReadBackend for LocalBackend { file.read_exact(&mut vec).map_err(|err| { RusticError::with_source( ErrorKind::Backend, - "Failed to read the exact length of the file. Please check the file and try again.", + "Failed to read the exact length `{length}` of the file `{path}`. Please check the file and try again.", err, ) .attach_context("path", self.path(tpe, id).to_string_lossy()) @@ -406,7 +411,7 @@ impl WriteBackend for LocalBackend { fs::create_dir_all(&self.path).map_err(|err| { RusticError::with_source( ErrorKind::InputOutput, - "Failed to create the directory. Please check the path and try again.", + "Failed to create the directory `{path}`. Please check the path and try again.", err, ) .attach_context("path", self.path.display().to_string()) @@ -417,7 +422,7 @@ impl WriteBackend for LocalBackend { fs::create_dir_all(path.clone()).map_err(|err| { RusticError::with_source( ErrorKind::InputOutput, - "Failed to create the directory. Please check the path and try again.", + "Failed to create the directory `{path}`. Please check the path and try again.", err, ) .attach_context("path", path.display().to_string()) @@ -429,7 +434,7 @@ impl WriteBackend for LocalBackend { fs::create_dir_all(path.clone()).map_err(|err| { RusticError::with_source( ErrorKind::InputOutput, - "Failed to create the directory. Please check the path and try again.", + "Failed to create the directory `{path}`. Please check the path and try again.", err, ) .attach_context("path", path.display().to_string()) @@ -472,21 +477,25 @@ impl WriteBackend for LocalBackend { .map_err(|err| { RusticError::with_source( ErrorKind::InputOutput, - "Failed to open the file. Please check the file and try again.", + "Failed to open the file `{path}`. Please check the file and try again.", err, ) .attach_context("path", filename.to_string_lossy()) })?; file.set_len(buf.len().try_into().map_err(|err| { - RusticError::with_source(ErrorKind::Internal, "Failed to convert length to u64.", err) - .attach_context("length", buf.len().to_string()) - .ask_report() + RusticError::with_source( + ErrorKind::Internal, + "Failed to convert length `{length}` to u64.", + err, + ) + .attach_context("length", buf.len().to_string()) + .ask_report() })?) .map_err(|err| { RusticError::with_source( ErrorKind::InputOutput, - "Failed to set the length of the file. Please check the file and try again.", + "Failed to set the length of the file `{path}`. Please check the file and try again.", err, ) .attach_context("path", filename.to_string_lossy()) @@ -495,7 +504,7 @@ impl WriteBackend for LocalBackend { file.write_all(&buf).map_err(|err| { RusticError::with_source( ErrorKind::InputOutput, - "Failed to write to the buffer. Please check the file and try again.", + "Failed to write to the buffer: `{path}`. Please check the file and try again.", err, ) .attach_context("path", filename.to_string_lossy()) @@ -504,7 +513,7 @@ impl WriteBackend for LocalBackend { file.sync_all().map_err(|err| { RusticError::with_source( ErrorKind::InputOutput, - "Failed to sync OS Metadata to disk. Please check the file and try again.", + "Failed to sync OS Metadata to disk: `{path}`. Please check the file and try again.", err, ) .attach_context("path", filename.to_string_lossy()) @@ -535,7 +544,7 @@ impl WriteBackend for LocalBackend { fs::remove_file(&filename).map_err(|err| RusticError::with_source( ErrorKind::Backend, - "Failed to remove the file. Was the file already removed or is it in use? Please check the file and remove it manually.", + "Failed to remove the file `{path}`. Was the file already removed or is it in use? Please check the file and remove it manually.", err ) .attach_context("path", filename.to_string_lossy()) diff --git a/crates/backend/src/opendal.rs b/crates/backend/src/opendal.rs index a0b8a333..e1fb71fa 100644 --- a/crates/backend/src/opendal.rs +++ b/crates/backend/src/opendal.rs @@ -55,21 +55,24 @@ impl FromStr for Throttle { ByteSize::from_str(s.trim()).map_err(|err| { RusticError::with_source( ErrorKind::InvalidInput, - "Parsing ByteSize from throttle string failed", + "Parsing ByteSize from throttle string `{string}` failed", err, ) .attach_context("string", s) }) }) .map(|b| -> RusticResult { - b?.as_u64().try_into().map_err(|err| { + let bytesize = b?.as_u64(); + bytesize.try_into().map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Converting ByteSize to u32 failed", + "Converting ByteSize `{bytesize}` to u32 failed", err, ) + .attach_context("bytesize", bytesize.to_string()) }) }); + let bandwidth = values .next() .transpose()? @@ -106,7 +109,7 @@ impl OpenDALBackend { Some(value) => usize::from_str(value).map_err(|err| { RusticError::with_source( ErrorKind::InvalidInput, - "Parsing retry value failed, the value must be a valid integer.", + "Parsing retry value `{value}` failed, the value must be a valid integer.", err, ) .attach_context("value", value.to_string()) @@ -118,7 +121,7 @@ impl OpenDALBackend { usize::from_str(c).map_err(|err| { RusticError::with_source( ErrorKind::InvalidInput, - "Parsing connections value failed, the value must be a valid integer.", + "Parsing connections value `{value}` failed, the value must be a valid integer.", err, ) .attach_context("value", c.to_string()) @@ -134,7 +137,7 @@ impl OpenDALBackend { let schema = Scheme::from_str(path.as_ref()).map_err(|err| { RusticError::with_source( ErrorKind::InvalidInput, - "Parsing scheme from path failed, the path must contain a valid scheme.", + "Parsing scheme from path `{path}` failed, the path must contain a valid scheme.", err, ) .attach_context("path", path.as_ref().to_string()) @@ -143,7 +146,7 @@ impl OpenDALBackend { .map_err(|err| { RusticError::with_source( ErrorKind::Backend, - "Creating Operator failed. Please check the given schema and options.", + "Creating Operator from path `{path}` failed. Please check the given schema and options.", err, ) .attach_context("path", path.as_ref().to_string()) @@ -244,7 +247,7 @@ impl ReadBackend for OpenDALBackend { .map_err(|err| { RusticError::with_source( ErrorKind::Backend, - "Listing all files failed in the backend. Please check if the given path is correct.", + "Listing all files of `{path}` failed in the backend. Please check if the given path is correct.", err, ) .attach_context("path", path) @@ -271,17 +274,17 @@ impl ReadBackend for OpenDALBackend { entry.content_length().try_into().map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Parsing content length failed", + "Parsing content length `{length}` failed", err, ) - .attach_context("content length", entry.content_length().to_string()) + .attach_context("length", entry.content_length().to_string()) })?, )]), Err(err) if err.kind() == opendal::ErrorKind::NotFound => Ok(Vec::new()), Err(err) => Err(err).map_err(|err| RusticError::with_source( ErrorKind::Backend, - "Getting Metadata of `config` failed in the backend. Please check if the `config` exists.", + "Getting Metadata of type `{type}` failed in the backend. Please check if `{type}` exists.", err, ) .attach_context("type", tpe.to_string()) @@ -299,7 +302,7 @@ impl ReadBackend for OpenDALBackend { .map_err(|err| RusticError::with_source( ErrorKind::Backend, - "Listing all files in directory and their sizes failed in the backend. Please check if the given path is correct.", + "Listing all files of `{type}` in directory `{path}` and their sizes failed in the backend. Please check if the given path is correct.", err, ) .attach_context("path", path) @@ -316,10 +319,10 @@ impl ReadBackend for OpenDALBackend { .map_err(|err| RusticError::with_source( ErrorKind::Internal, - "Parsing content length failed", + "Parsing content length `{length}` failed", err, ) - .attach_context("content length", e.metadata().content_length().to_string()) + .attach_context("length", e.metadata().content_length().to_string()) )?, )) }) @@ -342,7 +345,7 @@ impl ReadBackend for OpenDALBackend { .map_err(|err| RusticError::with_source( ErrorKind::Backend, - "Reading file failed in the backend. Please check if the given path is correct.", + "Reading file `{path}` failed in the backend. Please check if the given path is correct.", err, ) .attach_context("path", path) @@ -372,7 +375,7 @@ impl ReadBackend for OpenDALBackend { .map_err(|err| RusticError::with_source( ErrorKind::Backend, - "Partially reading file failed in the backend. Please check if the given path is correct.", + "Partially reading file `{path}` failed in the backend. Please check if the given path is correct.", err, ) .attach_context("path", path) @@ -397,11 +400,11 @@ impl WriteBackend for OpenDALBackend { .map_err(|err| RusticError::with_source( ErrorKind::Backend, - "Creating directory failed in the backend. Please check if the given path is correct.", + "Creating directory `{path}` failed in the backend `{location}`. Please check if the given path is correct.", err, ) - .attach_context("location", self.location()) .attach_context("path", path) + .attach_context("location", self.location()) .attach_context("type", tpe.to_string()) )?; } @@ -418,11 +421,11 @@ impl WriteBackend for OpenDALBackend { self.operator.create_dir(&path).map_err(|err| RusticError::with_source( ErrorKind::Backend, - "Creating directory failed in the backend. Please check if the given path is correct.", + "Creating directory `{path}` failed in the backend `{location}`. Please check if the given path is correct.", err, ) - .attach_context("location", self.location()) .attach_context("path", path) + .attach_context("location", self.location()) ) })?; @@ -449,7 +452,7 @@ impl WriteBackend for OpenDALBackend { self.operator.write(&filename, buf).map_err(|err| { RusticError::with_source( ErrorKind::Backend, - "Writing file failed in the backend. Please check if the given path is correct.", + "Writing file `{path}` failed in the backend. Please check if the given path is correct.", err, ) .attach_context("path", filename) @@ -473,7 +476,7 @@ impl WriteBackend for OpenDALBackend { self.operator.delete(&filename).map_err(|err| { RusticError::with_source( ErrorKind::Backend, - "Deleting file failed in the backend. Please check if the given path is correct.", + "Deleting file `{path}` failed in the backend. Please check if the given path is correct.", err, ) .attach_context("path", filename) diff --git a/crates/backend/src/rclone.rs b/crates/backend/src/rclone.rs index 60b97b7d..24836cf4 100644 --- a/crates/backend/src/rclone.rs +++ b/crates/backend/src/rclone.rs @@ -85,7 +85,7 @@ fn check_clone_version(rclone_version_output: &[u8]) -> RusticResult<()> { let mut parsed_version = Version::parse(rclone_version).map_err(|err| { RusticError::with_source(ErrorKind::Internal, - "Error parsing rclone version. This should not happen. Please check the `rclone version` output manually.", + "Error parsing rclone version `{version}`. This should not happen. Please check the `rclone version` output manually.", err) .attach_context("version", rclone_version) })?; @@ -113,9 +113,9 @@ fn check_clone_version(rclone_version_output: &[u8]) -> RusticResult<()> { { return Err(RusticError::new( ErrorKind::Unsupported, - "Unsupported rclone version. We must not use rclone without authentication! Please upgrade to rclone >= 1.52.2!", + "Unsupported rclone version `{version}`. We must not use rclone without authentication! Please upgrade to rclone >= 1.52.2!", ) - .attach_context("current version", rclone_version.to_string())); + .attach_context("version", rclone_version.to_string())); } Ok(()) @@ -205,10 +205,10 @@ impl RcloneBackend { .map_err(|err| RusticError::with_source( ErrorKind::ExternalCommand, - "Experienced an error while running rclone. Please check if rclone is installed and working correctly.", + "Experienced an error while running rclone: `{rclone_command}`. Please check if rclone is installed and working correctly.", err ) - .attach_context("rclone command", rclone_command.to_string()) + .attach_context("rclone_command", rclone_command.to_string()) )?; let mut stderr = BufReader::new( @@ -234,8 +234,8 @@ impl RcloneBackend { return Err( RusticError::new( ErrorKind::ExternalCommand, - "rclone exited before it could start the REST server. Please check the exit status for more information.", - ).attach_context("exit status", status.to_string()) + "rclone exited before it could start the REST server: `{exit_status}`. Please check the exit status for more information.", + ).attach_context("exit_status", status.to_string()) ); } let mut line = String::new(); @@ -270,7 +270,7 @@ impl RcloneBackend { if !rest_url.starts_with("http://") { return Err(RusticError::new( ErrorKind::InputOutput, - "Please make sure, the URL starts with 'http://'!", + "Please make sure, the URL `{url}` starts with 'http://'!", ) .attach_context("url", rest_url)); } diff --git a/crates/backend/src/rest.rs b/crates/backend/src/rest.rs index b9357ca9..2293afa6 100644 --- a/crates/backend/src/rest.rs +++ b/crates/backend/src/rest.rs @@ -155,7 +155,7 @@ impl RestBackend { }; let url = Url::parse(&url).map_err(|err| { - RusticError::with_source(ErrorKind::InvalidInput, "URL parsing failed", err) + RusticError::with_source(ErrorKind::InvalidInput, "URL `{url}` parsing failed", err) .attach_context("url", url) })?; @@ -180,7 +180,7 @@ impl RestBackend { _ => usize::from_str(&value).map_err(|err| { RusticError::with_source( ErrorKind::InvalidInput, - "Cannot parse value, invalid value for option retry.", + "Cannot parse value `{value}`, invalid value for option `{option}`.", err, ) .attach_context("value", value) @@ -192,7 +192,7 @@ impl RestBackend { let timeout = humantime::Duration::from_str(&value).map_err(|err| { RusticError::with_source( ErrorKind::InvalidInput, - "Could not parse value as `humantime` duration.", + "Could not parse value `{value}` as `humantime` duration. Invalid value for option `{option}`.", err, ) .attach_context("value", value) @@ -293,10 +293,10 @@ impl ReadBackend for RestBackend { }; let url = self.url.join(&path).map_err(|err| { - RusticError::with_source(ErrorKind::Internal, "Joining URL failed", err) + RusticError::with_source(ErrorKind::Internal, "Joining URL `{url}` failed", err) .attach_context("url", self.url.as_str()) .attach_context("tpe", tpe.to_string()) - .attach_context("tpe dir", tpe.dirname().to_string()) + .attach_context("tpe_dir", tpe.dirname().to_string()) })?; backoff::retry_notify( @@ -391,10 +391,10 @@ impl ReadBackend for RestBackend { let offset2 = offset + length - 1; let header_value = format!("bytes={offset}-{offset2}"); let url = self.url(tpe, id).map_err(|err| { - RusticError::with_source(ErrorKind::Internal, "Joining URL failed", err) + RusticError::with_source(ErrorKind::Internal, "Joining URL `{url}` failed", err) .attach_context("url", self.url.as_str()) .attach_context("tpe", tpe.to_string()) - .attach_context("tpe dir", tpe.dirname().to_string()) + .attach_context("tpe_dir", tpe.dirname().to_string()) .attach_context("id", id.to_string()) })?; @@ -429,10 +429,10 @@ fn construct_join_url_error( id: &Id, self_url: &Url, ) -> Box { - RusticError::with_source(ErrorKind::Internal, "Joining URL failed", err) + RusticError::with_source(ErrorKind::Internal, "Joining URL `{url}` failed", err) .attach_context("url", self_url.as_str()) .attach_context("tpe", tpe.to_string()) - .attach_context("tpe dir", tpe.dirname().to_string()) + .attach_context("tpe_dir", tpe.dirname().to_string()) .attach_context("id", id.to_string()) } @@ -444,9 +444,13 @@ impl WriteBackend for RestBackend { /// * If the backoff failed. fn create(&self) -> RusticResult<()> { let url = self.url.join("?create=true").map_err(|err| { - RusticError::with_source(ErrorKind::Internal, "Joining URL failed", err) - .attach_context("url", self.url.as_str()) - .attach_context("join input", "?create=true") + RusticError::with_source( + ErrorKind::Internal, + "Joining URL `{url}` with `{join_input}` failed", + err, + ) + .attach_context("url", self.url.as_str()) + .attach_context("join_input", "?create=true") })?; backoff::retry_notify( diff --git a/crates/backend/src/util.rs b/crates/backend/src/util.rs index 4e725a4d..d1ff6928 100644 --- a/crates/backend/src/util.rs +++ b/crates/backend/src/util.rs @@ -61,7 +61,7 @@ pub fn location_to_type_and_path( SupportedBackend::try_from(scheme).map_err(|err| { RusticError::with_source( ErrorKind::Unsupported, - "The backend type is not supported. Please check the given backend and try again.", + "The backend type `{name}` is not supported. Please check the given backend and try again.", err ) .attach_context("name", scheme) diff --git a/crates/core/src/archiver/file_archiver.rs b/crates/core/src/archiver/file_archiver.rs index d0e7ba6b..33a614bb 100644 --- a/crates/core/src/archiver/file_archiver.rs +++ b/crates/core/src/archiver/file_archiver.rs @@ -118,7 +118,7 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> FileArchiver<'a, BE, I> { .ok_or_else( || RusticError::new( ErrorKind::Internal, - "Failed to unpack tree type optional. Option should contain a value, but contained `None`.", + "Failed to unpack tree type optional at `{path}`. Option should contain a value, but contained `None`.", ) .attach_context("path", path.display().to_string()) .ask_report(), @@ -127,7 +127,7 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> FileArchiver<'a, BE, I> { .map_err(|err| { RusticError::with_source( ErrorKind::InputOutput, - "Failed to open ReadSourceOpen", + "Failed to open ReadSourceOpen at `{path}`", err, ) .attach_context("path", path.display().to_string()) @@ -154,7 +154,7 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> FileArchiver<'a, BE, I> { usize::try_from(node.meta.size).map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to convert node size to usize", + "Failed to convert node size `{size}` to usize", err, ) .attach_context("size", node.meta.size.to_string()) diff --git a/crates/core/src/archiver/tree_archiver.rs b/crates/core/src/archiver/tree_archiver.rs index 278f02b1..df2ee52a 100644 --- a/crates/core/src/archiver/tree_archiver.rs +++ b/crates/core/src/archiver/tree_archiver.rs @@ -166,9 +166,13 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> TreeArchiver<'a, BE, I> { /// The id of the tree. fn backup_tree(&mut self, path: &Path, parent: &ParentResult) -> RusticResult { let (chunk, id) = self.tree.serialize().map_err(|err| { - RusticError::with_source(ErrorKind::Internal, "Failed to serialize tree.", err) - .attach_context("path", path.to_string_lossy()) - .ask_report() + RusticError::with_source( + ErrorKind::Internal, + "Failed to serialize tree at `{path}`", + err, + ) + .attach_context("path", path.to_string_lossy()) + .ask_report() })?; let dirsize = chunk.len() as u64; let dirsize_bytes = ByteSize(dirsize).to_string_as(true); diff --git a/crates/core/src/backend.rs b/crates/core/src/backend.rs index 67abe40d..2179671b 100644 --- a/crates/core/src/backend.rs +++ b/crates/core/src/backend.rs @@ -231,11 +231,14 @@ pub trait FindInBackend: ReadBackend { MapResult::Some(id) => Ok(id), MapResult::None => Err(RusticError::new( ErrorKind::Backend, - "No suitable id found.", + "No suitable id found for `{id}`.", ) - .attach_context("item", vec[i].as_ref().to_string())), - MapResult::NonUnique => Err(RusticError::new(ErrorKind::Backend, "Id not unique.") - .attach_context("item", vec[i].as_ref().to_string())), + .attach_context("id", vec[i].as_ref().to_string())), + MapResult::NonUnique => Err(RusticError::new( + ErrorKind::Backend, + "Id not unique: `{id}`.", + ) + .attach_context("id", vec[i].as_ref().to_string())), }) .collect() } diff --git a/crates/core/src/backend/cache.rs b/crates/core/src/backend/cache.rs index 4cfd4574..e8ec526e 100644 --- a/crates/core/src/backend/cache.rs +++ b/crates/core/src/backend/cache.rs @@ -245,7 +245,7 @@ impl Cache { fs::create_dir_all(&path).map_err(|err| { RusticError::with_source( ErrorKind::InputOutput, - "Failed to create cache directory", + "Failed to create cache directory at `{path}`", err, ) .attach_context("path", path.display().to_string()) @@ -255,7 +255,7 @@ impl Cache { cachedir::ensure_tag(&path).map_err(|err| { RusticError::with_source( ErrorKind::InputOutput, - "Failed to ensure cache directory tag", + "Failed to ensure cache directory tag at `{path}`", err, ) .attach_context("path", path.display().to_string()) @@ -267,7 +267,7 @@ impl Cache { fs::create_dir_all(&path).map_err(|err| { RusticError::with_source( ErrorKind::InputOutput, - "Failed to create cache directory with id", + "Failed to create cache directory with id `{id}` at `{path}`", err, ) .attach_context("path", path.display().to_string()) @@ -409,7 +409,7 @@ impl Cache { Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None), Err(err) => Err(RusticError::with_source( ErrorKind::InputOutput, - "Failed to read full data of file", + "Failed to read full data of file at `{path}`", err, ) .attach_context("path", path.display().to_string()) @@ -444,15 +444,18 @@ impl Cache { &offset ); - let mut file = match File::open(self.path(tpe, id)) { + let path = self.path(tpe, id); + + let mut file = match File::open(&path) { Ok(file) => file, Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(None), Err(err) => { return Err(RusticError::with_source( ErrorKind::InputOutput, - "Failed to open file", + "Failed to open file at `{path}`", err, ) + .attach_context("path", path.display().to_string()) .attach_context("tpe", tpe.to_string()) .attach_context("id", id.to_string())) } @@ -461,20 +464,29 @@ impl Cache { _ = file .seek(SeekFrom::Start(u64::from(offset))) .map_err(|err| { - RusticError::with_source(ErrorKind::InputOutput, "Failed to seek in file", err) - .attach_context("tpe", tpe.to_string()) - .attach_context("id", id.to_string()) - .attach_context("offset", offset.to_string()) + RusticError::with_source( + ErrorKind::InputOutput, + "Failed to seek to `{offset}` in file `{path}`", + err, + ) + .attach_context("path", path.display().to_string()) + .attach_context("tpe", tpe.to_string()) + .attach_context("id", id.to_string()) + .attach_context("offset", offset.to_string()) })?; let mut vec = vec![0; length as usize]; file.read_exact(&mut vec).map_err(|err| { - RusticError::with_source(ErrorKind::InputOutput, "Failed to read from file", err) - .attach_context("tpe", tpe.to_string()) - .attach_context("id", id.to_string()) - .attach_context("offset", offset.to_string()) - .attach_context("length", length.to_string()) + RusticError::with_source( + ErrorKind::InputOutput, + "Failed to read at offset `{offset}` from file at `{path}`", + err, + ) + .attach_context("tpe", tpe.to_string()) + .attach_context("id", id.to_string()) + .attach_context("offset", offset.to_string()) + .attach_context("length", length.to_string()) })?; trace!("cache hit!"); @@ -499,10 +511,14 @@ impl Cache { let dir = self.dir(tpe, id); fs::create_dir_all(&dir).map_err(|err| { - RusticError::with_source(ErrorKind::InputOutput, "Failed to create directories", err) - .attach_context("path", dir.display().to_string()) - .attach_context("tpe", tpe.to_string()) - .attach_context("id", id.to_string()) + RusticError::with_source( + ErrorKind::InputOutput, + "Failed to create directories at `{path}`", + err, + ) + .attach_context("path", dir.display().to_string()) + .attach_context("tpe", tpe.to_string()) + .attach_context("id", id.to_string()) })?; let filename = self.path(tpe, id); @@ -513,15 +529,23 @@ impl Cache { .write(true) .open(&filename) .map_err(|err| { - RusticError::with_source(ErrorKind::InputOutput, "Failed to open file", err) - .attach_context("path", filename.display().to_string()) + RusticError::with_source( + ErrorKind::InputOutput, + "Failed to open file at `{path}`", + err, + ) + .attach_context("path", filename.display().to_string()) })?; file.write_all(buf).map_err(|err| { - RusticError::with_source(ErrorKind::InputOutput, "Failed to write to buffer", err) - .attach_context("path", filename.display().to_string()) - .attach_context("tpe", tpe.to_string()) - .attach_context("id", id.to_string()) + RusticError::with_source( + ErrorKind::InputOutput, + "Failed to write to buffer at `{path}`", + err, + ) + .attach_context("path", filename.display().to_string()) + .attach_context("tpe", tpe.to_string()) + .attach_context("id", id.to_string()) })?; Ok(()) @@ -541,10 +565,14 @@ impl Cache { trace!("cache writing tpe: {:?}, id: {}", &tpe, &id); let filename = self.path(tpe, id); fs::remove_file(&filename).map_err(|err| { - RusticError::with_source(ErrorKind::InputOutput, "Failed to remove file", err) - .attach_context("path", filename.display().to_string()) - .attach_context("tpe", tpe.to_string()) - .attach_context("id", id.to_string()) + RusticError::with_source( + ErrorKind::InputOutput, + "Failed to remove file at `{path}`", + err, + ) + .attach_context("path", filename.display().to_string()) + .attach_context("tpe", tpe.to_string()) + .attach_context("id", id.to_string()) })?; Ok(()) diff --git a/crates/core/src/backend/decrypt.rs b/crates/core/src/backend/decrypt.rs index c6df00ab..414084f4 100644 --- a/crates/core/src/backend/decrypt.rs +++ b/crates/core/src/backend/decrypt.rs @@ -81,10 +81,11 @@ pub trait DecryptReadBackend: ReadBackend + Clone + 'static { err, ) })?; + if data.len() != length.get() as usize { return Err(RusticError::new( ErrorKind::Internal, - "Length of uncompressed data does not match the given length.", + "Length of uncompressed data `{actual_length}` does not match the given length `{expected_length}`.", ) .attach_context("expected_length", length.get().to_string()) .attach_context("actual_length", data.len().to_string()) @@ -423,7 +424,7 @@ impl DecryptBackend { "Compressing and appending data failed. The data may be corrupted.", err, ) - .attach_context("compression level", level.to_string()) + .attach_context("compression_level", level.to_string()) })?; self.key().encrypt_data(&out)? @@ -453,7 +454,7 @@ impl DecryptBackend { let data_len: u32 = data.len().try_into().map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to convert data length to u32.", + "Failed to convert data length `{length}` to u32.", err, ) .attach_context("length", data.len().to_string()) @@ -471,7 +472,7 @@ impl DecryptBackend { "Failed to encode zstd compressed data. The data may be corrupted.", err, ) - .attach_context("compression level", level.to_string()) + .attach_context("compression_level", level.to_string()) })?)?, NonZeroU32::new(data_len), ), diff --git a/crates/core/src/backend/ignore.rs b/crates/core/src/backend/ignore.rs index 1d37cf75..a3c5cf2d 100644 --- a/crates/core/src/backend/ignore.rs +++ b/crates/core/src/backend/ignore.rs @@ -192,7 +192,7 @@ impl LocalSource { _ = override_builder.add(g).map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to add glob pattern to override builder.", + "Failed to add glob pattern `{glob}` to override builder.", err, ) .attach_context("glob", g.to_string()) @@ -205,10 +205,10 @@ impl LocalSource { .map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to read string from glob file.", + "Failed to read string from glob file at `{glob_file}`", err, ) - .attach_context("glob file", file.to_string()) + .attach_context("glob_file", file.to_string()) .ask_report() })? .lines() @@ -216,10 +216,10 @@ impl LocalSource { _ = override_builder.add(line).map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to add glob pattern line to override builder.", + "Failed to add glob pattern line `{glob_pattern_line}` to override builder.", err, ) - .attach_context("glob pattern line", line.to_string()) + .attach_context("glob_pattern_line", line.to_string()) .ask_report() })?; } @@ -237,7 +237,7 @@ impl LocalSource { _ = override_builder.add(g).map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to add iglob pattern to override builder.", + "Failed to add iglob pattern `{iglob}` to override builder.", err, ) .attach_context("iglob", g.to_string()) @@ -250,10 +250,10 @@ impl LocalSource { .map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to read string from iglob file.", + "Failed to read string from iglob file at `{iglob_file}`", err, ) - .attach_context("iglob file", file.to_string()) + .attach_context("iglob_file", file.to_string()) .ask_report() })? .lines() @@ -261,10 +261,10 @@ impl LocalSource { _ = override_builder.add(line).map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to add iglob pattern line to override builder.", + "Failed to add iglob pattern line `{iglob_pattern_line}` to override builder.", err, ) - .attach_context("iglob pattern line", line.to_string()) + .attach_context("iglob_pattern_line", line.to_string()) .ask_report() })?; } @@ -334,10 +334,10 @@ impl ReadSourceOpen for OpenFile { File::open(&path).map_err(|err| { RusticError::with_source( ErrorKind::InputOutput, - "Failed to open file. Please make sure the file exists and is accessible.", + "Failed to open file at `{path}`. Please make sure the file exists and is accessible.", err, ) - .attach_context("file", path.display().to_string()) + .attach_context("path", path.display().to_string()) }) } } diff --git a/crates/core/src/backend/local_destination.rs b/crates/core/src/backend/local_destination.rs index 837f7d55..bef3852b 100644 --- a/crates/core/src/backend/local_destination.rs +++ b/crates/core/src/backend/local_destination.rs @@ -154,7 +154,7 @@ impl LocalDestination { fs::create_dir_all(path).map_err(|err| { RusticError::with_source( ErrorKind::InputOutput, - "The directory could not be created.", + "The directory `{path}` could not be created.", err, ) .attach_context("path", path.display().to_string()) @@ -164,7 +164,7 @@ impl LocalDestination { fs::create_dir_all(&path).map_err(|err| { RusticError::with_source( ErrorKind::InputOutput, - "The directory could not be created.", + "The directory `{path}` could not be created.", err, ) .attach_context("path", path.display().to_string()) diff --git a/crates/core/src/blob/packer.rs b/crates/core/src/blob/packer.rs index 4295434d..33a26ed4 100644 --- a/crates/core/src/blob/packer.rs +++ b/crates/core/src/blob/packer.rs @@ -301,9 +301,13 @@ impl Packer { pub fn add(&self, data: Bytes, id: BlobId) -> RusticResult<()> { // compute size limit based on total size and size bounds self.add_with_sizelimit(data, id, None).map_err(|err| { - RusticError::with_source(ErrorKind::Internal, "Failed to add blob to packfile.", err) - .attach_context("blob id", id.to_string()) - .ask_report() + RusticError::with_source( + ErrorKind::Internal, + "Failed to add blob `{id}` to packfile.", + err, + ) + .attach_context("id", id.to_string()) + .ask_report() }) } @@ -576,10 +580,10 @@ impl RawPacker { let data_len_packed: u64 = data.len().try_into().map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to convert data length to u64.", + "Failed to convert data length `{length}` to u64.", err, ) - .attach_context("data length", data.len().to_string()) + .attach_context("length", data.len().to_string()) })?; self.stats.data_packed += data_len_packed; @@ -591,12 +595,12 @@ impl RawPacker { let len = self.write_data(data).map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to write data to packfile.", + "Failed to write data to packfile for blob `{id}`.", err, ) - .attach_context("blob id", id.to_string()) - .attach_context("size limit", size_limit.to_string()) - .attach_context("data length packed", data_len_packed.to_string()) + .attach_context("id", id.to_string()) + .attach_context("size_limit", size_limit.to_string()) + .attach_context("data_length_packed", data_len_packed.to_string()) })?; self.index @@ -636,10 +640,10 @@ impl RawPacker { .map_err(|err| -> Box { RusticError::with_source( ErrorKind::Internal, - "Failed to convert pack header to binary representation.", + "Failed to convert pack header `{index_pack_id}` to binary representation.", err, ) - .attach_context("index pack id", self.index.id.to_string()) + .attach_context("index_pack_id", self.index.id.to_string()) })?; // encrypt and write to pack file @@ -648,20 +652,20 @@ impl RawPacker { let headerlen: u32 = data.len().try_into().map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to convert header length to u32.", + "Failed to convert header length `{length}` to u32.", err, ) - .attach_context("header length", data.len().to_string()) + .attach_context("length", data.len().to_string()) })?; // write header to pack file _ = self.write_data(&data).map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to write header to packfile.", + "Failed to write header with length `{length}` to packfile.", err, ) - .attach_context("header length", headerlen.to_string()) + .attach_context("length", headerlen.to_string()) })?; // convert header length to binary representation @@ -670,20 +674,20 @@ impl RawPacker { .map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to convert header length to binary representation.", + "Failed to convert header length `{length}` to binary representation.", err, ) - .attach_context("header length", headerlen.to_string()) + .attach_context("length", headerlen.to_string()) })?; // finally write length of header unencrypted to pack file _ = self.write_data(&binary_repr).map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to write header length to packfile.", + "Failed to write header length `{length}` to packfile.", err, ) - .attach_context("header length", headerlen.to_string()) + .attach_context("length", headerlen.to_string()) })?; Ok(()) diff --git a/crates/core/src/blob/tree.rs b/crates/core/src/blob/tree.rs index 211e721c..e28d079d 100644 --- a/crates/core/src/blob/tree.rs +++ b/crates/core/src/blob/tree.rs @@ -134,8 +134,11 @@ impl Tree { let data = index .get_tree(&id) .ok_or_else(|| { - RusticError::new(ErrorKind::Internal, "Blob ID not found in index") - .attach_context("tree id", id.to_string()) + RusticError::new( + ErrorKind::Internal, + "Tree ID `{tree_id}` not found in index", + ) + .attach_context("tree_id", id.to_string()) })? .read_data(be)?; @@ -177,14 +180,14 @@ impl Tree { if let Some(p) = comp_to_osstr(p).map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to convert Path component to OsString.", + "Failed to convert Path component `{path}` to OsString.", err, ) .attach_context("path", path.display().to_string()) .ask_report() })? { let id = node.subtree.ok_or_else(|| { - RusticError::new(ErrorKind::Internal, "Node is not a directory.") + RusticError::new(ErrorKind::Internal, "Node `{node}` is not a directory.") .attach_context("node", p.to_string_lossy()) .ask_report() })?; @@ -194,7 +197,7 @@ impl Tree { .into_iter() .find(|node| node.name() == p) .ok_or_else(|| { - RusticError::new(ErrorKind::Internal, "Node not found in tree.") + RusticError::new(ErrorKind::Internal, "Node `{node}` not found in tree.") .attach_context("node", p.to_string_lossy()) .ask_report() })?; @@ -236,9 +239,12 @@ impl Tree { Some(*node_idx) } else { let id = node.subtree.ok_or_else(|| { - RusticError::new(ErrorKind::Internal, "Subtree ID not found.") - .attach_context("node", path_comp[idx].to_string_lossy()) - .ask_report() + RusticError::new( + ErrorKind::Internal, + "Subtree ID not found for node `{node}`", + ) + .attach_context("node", path_comp[idx].to_string_lossy()) + .ask_report() })?; find_node_from_component( @@ -265,7 +271,7 @@ impl Tree { .map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to convert Path component to OsString.", + "Failed to convert Path component `{path}` to OsString.", err, ) .attach_context("path", path.display().to_string()) @@ -343,9 +349,12 @@ impl Tree { let node_path = path.join(node.name()); if node.is_dir() { let id = node.subtree.ok_or_else(|| { - RusticError::new(ErrorKind::Internal, "Subtree ID not found.") - .attach_context("node", node.name().to_string_lossy()) - .ask_report() + RusticError::new( + ErrorKind::Internal, + "Subtree ID not found for node `{node}`", + ) + .attach_context("node", node.name().to_string_lossy()) + .ask_report() })?; result.append(&mut find_matching_nodes_recursive( @@ -591,7 +600,7 @@ where _ = override_builder.add(g).map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to add glob pattern to override builder.", + "Failed to add glob pattern `{glob}` to override builder.", err, ) .attach_context("glob", g.to_string()) @@ -604,10 +613,10 @@ where .map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to read string from glob file.", + "Failed to read string from glob file `{glob_file}` ", err, ) - .attach_context("glob file", file.to_string()) + .attach_context("glob_file", file.to_string()) .ask_report() })? .lines() @@ -615,10 +624,10 @@ where _ = override_builder.add(line).map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to add glob pattern line to override builder.", + "Failed to add glob pattern line `{glob_pattern_line}` to override builder.", err, ) - .attach_context("glob pattern line", line.to_string()) + .attach_context("glob_pattern_line", line.to_string()) .ask_report() })?; } @@ -636,7 +645,7 @@ where _ = override_builder.add(g).map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to add iglob pattern to override builder.", + "Failed to add iglob pattern `{iglob}` to override builder.", err, ) .attach_context("iglob", g.to_string()) @@ -649,10 +658,10 @@ where .map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to read string from iglob file.", + "Failed to read string from iglob file `{iglob_file}`", err, ) - .attach_context("iglob file", file.to_string()) + .attach_context("iglob_file", file.to_string()) .ask_report() })? .lines() @@ -660,10 +669,10 @@ where _ = override_builder.add(line).map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to add iglob pattern line to override builder.", + "Failed to add iglob pattern line `{iglob_pattern_line}` to override builder.", err, ) - .attach_context("iglob pattern line", line.to_string()) + .attach_context("iglob_pattern_line", line.to_string()) .ask_report() })?; } @@ -806,10 +815,10 @@ impl TreeStreamerOnce

{ .map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to add tree ID to pending queue.", + "Failed to add tree ID `{tree_id}` to unbounded pending queue (`{count}`).", err, ) - .attach_context("tree id", id.to_string()) + .attach_context("tree_id", id.to_string()) .attach_context("count", count.to_string()) .ask_report() })? @@ -874,7 +883,7 @@ impl Iterator for TreeStreamerOnce

{ "Failed to receive tree from crossbeam channel.", err, ) - .attach_context("finished ids", self.finished_ids.to_string()) + .attach_context("finished_ids", self.finished_ids.to_string()) .ask_report())); } Ok(Err(err)) => return Some(Err(err)), @@ -890,11 +899,11 @@ impl Iterator for TreeStreamerOnce

{ return Some(Err(err).map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to add tree ID to pending queue.", + "Failed to add tree ID `{tree_id}` to pending queue (`{count}`).", err, ) .attach_context("path", path.display().to_string()) - .attach_context("tree id", id.to_string()) + .attach_context("tree_id", id.to_string()) .attach_context("count", count.to_string()) .ask_report() })) diff --git a/crates/core/src/commands/backup.rs b/crates/core/src/commands/backup.rs index dd999189..0b7e6602 100644 --- a/crates/core/src/commands/backup.rs +++ b/crates/core/src/commands/backup.rs @@ -205,6 +205,7 @@ pub struct BackupOptions { /// # Returns /// /// The snapshot pointing to the backup'ed data. +#[allow(clippy::too_many_lines)] pub(crate) fn backup( repo: &Repository, opts: &BackupOptions, @@ -228,7 +229,7 @@ pub(crate) fn backup( .map_err(|err| { RusticError::with_source( ErrorKind::InvalidInput, - "Failed to parse dotted path.", + "Failed to parse dotted path `{path}`", err, ) .attach_context("path", p.display().to_string()) @@ -239,19 +240,27 @@ pub(crate) fn backup( match &as_path { Some(p) => snap.paths.set_paths(&[p.clone()]).map_err(|err| { - RusticError::with_source(ErrorKind::Internal, "Failed to set paths in snapshot.", err) - .attach_context("paths", p.display().to_string()) + RusticError::with_source( + ErrorKind::Internal, + "Failed to set paths `{paths}` in snapshot.", + err, + ) + .attach_context("paths", p.display().to_string()) })?, None => snap.paths.set_paths(&backup_path).map_err(|err| { - RusticError::with_source(ErrorKind::Internal, "Failed to set paths in snapshot.", err) - .attach_context( - "paths", - backup_path - .iter() - .map(|p| p.display().to_string()) - .collect::>() - .join(","), - ) + RusticError::with_source( + ErrorKind::Internal, + "Failed to set paths `{paths}` in snapshot.", + err, + ) + .attach_context( + "paths", + backup_path + .iter() + .map(|p| p.display().to_string()) + .collect::>() + .join(","), + ) })?, }; diff --git a/crates/core/src/commands/cat.rs b/crates/core/src/commands/cat.rs index 8d355ea8..ae4a8cb1 100644 --- a/crates/core/src/commands/cat.rs +++ b/crates/core/src/commands/cat.rs @@ -107,7 +107,7 @@ pub(crate) fn cat_tree( let id = node.subtree.ok_or_else(|| { RusticError::new( ErrorKind::Command, - "Path in Node subtree is not a directory. Please provide a directory path.", + "Path `{path}` in Node subtree is not a directory. Please provide a directory path.", ) .attach_context("path", path.to_string()) })?; diff --git a/crates/core/src/commands/check.rs b/crates/core/src/commands/check.rs index 7d57d65f..242bea9b 100644 --- a/crates/core/src/commands/check.rs +++ b/crates/core/src/commands/check.rs @@ -149,7 +149,7 @@ impl FromStr for ReadSubsetOption { let percentage = p.parse().map_err(|err| { RusticError::with_source( ErrorKind::InvalidInput, - "Error parsing percentage for ReadSubset option. Did you forget the '%'?", + "Error parsing percentage from value `{value}` for ReadSubset option. Did you forget the '%'?", err, ) .attach_context("value", p.to_string()) @@ -162,7 +162,7 @@ impl FromStr for ReadSubsetOption { |err| RusticError::with_source( ErrorKind::InvalidInput, - "Error parsing n/m for ReadSubset option. Allowed values: 'all', 'x%', 'n/m' or a size.", + "Error parsing 'n/m' from value `{value}` for ReadSubset option. Allowed values: 'all', 'x%', 'n/m' or a size.", err ) .attach_context("value", s) @@ -176,7 +176,7 @@ impl FromStr for ReadSubsetOption { .map_err(|err| { RusticError::with_source( ErrorKind::InvalidInput, - "Error parsing size for ReadSubset option. Allowed values: 'all', 'x%', 'n/m' or a size.", + "Error parsing size from value `{value}` for ReadSubset option. Allowed values: 'all', 'x%', 'n/m' or a size.", err ) .attach_context("value", s) @@ -689,10 +689,14 @@ fn check_pack( let header_len = PackHeaderRef::from_index_pack(&index_pack).size(); let pack_header_len = PackHeaderLength::from_binary(&data.split_off(data.len() - 4)) .map_err(|err| { - RusticError::with_source(ErrorKind::Command, "Error reading pack header length.", err) - .attach_context("pack id", id.to_string()) - .attach_context("header length", header_len.to_string()) - .ask_report() + RusticError::with_source( + ErrorKind::Command, + "Error reading pack header length `{length}` for `{pack_id}`", + err, + ) + .attach_context("pack_id", id.to_string()) + .attach_context("length", header_len.to_string()) + .ask_report() })? .to_u32(); if pack_header_len != header_len { @@ -705,9 +709,13 @@ fn check_pack( let pack_blobs = PackHeader::from_binary(&header) .map_err(|err| { - RusticError::with_source(ErrorKind::Command, "Error reading pack header.", err) - .attach_context("pack id", id.to_string()) - .ask_report() + RusticError::with_source( + ErrorKind::Command, + "Error reading pack header for id `{pack_id}`", + err, + ) + .attach_context("pack_id", id.to_string()) + .ask_report() })? .into_blobs(); let mut blobs = index_pack.blobs; diff --git a/crates/core/src/commands/config.rs b/crates/core/src/commands/config.rs index 24adafab..fa6c0065 100644 --- a/crates/core/src/commands/config.rs +++ b/crates/core/src/commands/config.rs @@ -192,17 +192,17 @@ impl ConfigOptions { if !range.contains(&version) { return Err(RusticError::new( ErrorKind::Unsupported, - "Config version not supported.", + "Config version unsupported. Allowed versions are `{allowed_versions}`. You provided `{current_version}`. Please use a supported version. ", ) - .attach_context("current version", version.to_string()) - .attach_context("allowed versions", format!("{range:?}"))); + .attach_context("current_version", version.to_string()) + .attach_context("allowed_versions", format!("{range:?}"))); } else if version < config.version { return Err(RusticError::new( ErrorKind::Unsupported, - "Downgrading config version is not supported. Please use a higher version.", + "Downgrading config version is unsupported. You provided `{new_version}` which is smaller than `{current_version}`. Please use a version that is greater or equal to the current one.", ) - .attach_context("current version", config.version.to_string()) - .attach_context("new version", version.to_string())); + .attach_context("current_version", config.version.to_string()) + .attach_context("new_version", version.to_string())); } config.version = version; @@ -212,7 +212,7 @@ impl ConfigOptions { if config.version == 1 && compression != 0 { return Err(RusticError::new( ErrorKind::Unsupported, - "Compression not supported for v1 repos.", + "Compression `{compression}` unsupported for v1 repos.", ) .attach_context("compression", compression.to_string())); } @@ -221,10 +221,10 @@ impl ConfigOptions { if !range.contains(&compression) { return Err(RusticError::new( ErrorKind::Unsupported, - "Compression level not supported.", + "Compression level `{compression}` is unsupported. Allowed levels are `{allowed_levels}`. Please use a supported level.", ) .attach_context("compression", compression.to_string()) - .attach_context("allowed levels", format!("{range:?}"))); + .attach_context("allowed_levels", format!("{range:?}"))); } config.compression = Some(compression); } @@ -273,7 +273,7 @@ impl ConfigOptions { if percent > 100 { return Err(RusticError::new( ErrorKind::InvalidInput, - "`min_packsize_tolerate_percent` must be <= 100.", + "`min_packsize_tolerate_percent` must be <= 100. You provided `{percent}`.", ) .attach_context("percent", percent.to_string())); } @@ -285,7 +285,7 @@ impl ConfigOptions { if percent < 100 && percent > 0 { return Err(RusticError::new( ErrorKind::InvalidInput, - "`max_packsize_tolerate_percent` must be >= 100 or 0.", + "`max_packsize_tolerate_percent` must be >= 100 or 0. You provided `{percent}`.", ) .attach_context("percent", percent.to_string())); } @@ -304,7 +304,7 @@ fn construct_size_too_large_error( ) -> Box { RusticError::with_source( ErrorKind::Internal, - "Failed to convert ByteSize to u64. Size is too large.", + "Failed to convert ByteSize `{size}` to u64. Size is too large.", err, ) .attach_context("size", size.to_string()) diff --git a/crates/core/src/commands/dump.rs b/crates/core/src/commands/dump.rs index 5725a2f5..dac2f347 100644 --- a/crates/core/src/commands/dump.rs +++ b/crates/core/src/commands/dump.rs @@ -31,9 +31,9 @@ pub(crate) fn dump( if node.node_type != NodeType::File { return Err(RusticError::new( ErrorKind::Unsupported, - "Dump is not supported for non-file node types. You could try to use `cat` instead.", + "Dump is not supported for non-file node types `{node_type}`. You could try to use `cat` instead.", ) - .attach_context("node type", node.node_type.to_string())); + .attach_context("node_type", node.node_type.to_string())); } for id in node.content.as_ref().unwrap() { diff --git a/crates/core/src/commands/merge.rs b/crates/core/src/commands/merge.rs index b4a68ac5..10d15dd1 100644 --- a/crates/core/src/commands/merge.rs +++ b/crates/core/src/commands/merge.rs @@ -45,8 +45,12 @@ pub(crate) fn merge_snapshots( .merge(); snap.paths.set_paths(&paths.paths()).map_err(|err| { - RusticError::with_source(ErrorKind::Internal, "Failed to set paths in snapshot.", err) - .attach_context("paths", paths.to_string()) + RusticError::with_source( + ErrorKind::Internal, + "Failed to set paths `{paths}` in snapshot.", + err, + ) + .attach_context("paths", paths.to_string()) })?; // set snapshot time to time of latest snapshot to be merged @@ -116,10 +120,10 @@ pub(crate) fn merge_trees( let size = u64::try_from(chunk.len()).map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to convert chunk length to u64.", + "Failed to convert chunk length `{length}` to u64.", err, ) - .attach_context("chunk length", chunk.len().to_string()) + .attach_context("length", chunk.len().to_string()) })?; if !index.has_tree(&new_id) { diff --git a/crates/core/src/commands/prune.rs b/crates/core/src/commands/prune.rs index a375d7f5..56ee7a73 100644 --- a/crates/core/src/commands/prune.rs +++ b/crates/core/src/commands/prune.rs @@ -203,7 +203,7 @@ impl FromStr for LimitOption { copy.parse().map_err(|err| { RusticError::with_source( ErrorKind::InvalidInput, - "Failed to parse percentage limit.", + "Failed to parse percentage limit `{limit}`", err, ) .attach_context("limit", s) @@ -214,7 +214,7 @@ impl FromStr for LimitOption { let byte_size = ByteSize::from_str(s).map_err(|err| { RusticError::with_source( ErrorKind::InvalidInput, - "Failed to parse size limit.", + "Failed to parse size limit `{limit}`", err, ) .attach_context("limit", s) @@ -704,9 +704,9 @@ impl PrunePlan { if version < 2 && opts.repack_uncompressed { return Err(RusticError::new( ErrorKind::Unsupported, - "Repacking uncompressed pack is unsupported in Repository version 1.", + "Repacking uncompressed pack is unsupported in Repository version `{config_version}`.", ) - .attach_context("config version", version.to_string())); + .attach_context("config_version", version.to_string())); } let mut index_files = Vec::new(); @@ -754,7 +754,7 @@ impl PrunePlan { Duration::from_std(*opts.keep_pack).map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to convert keep_pack duration to std::time::Duration.", + "Failed to convert keep_pack duration `{keep_pack}` to std::time::Duration.", err, ) .attach_context("keep_pack", opts.keep_pack.to_string()) @@ -762,7 +762,7 @@ impl PrunePlan { Duration::from_std(*opts.keep_delete).map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to convert keep_delete duration to std::time::Duration.", + "Failed to convert keep_delete duration `{keep_delete}` to std::time::Duration.", err, ) .attach_context("keep_delete", opts.keep_delete.to_string()) @@ -814,9 +814,9 @@ impl PrunePlan { if *count == 0 { return Err(RusticError::new( ErrorKind::Command, - "Blob is missing in index files.", + "Blob ID `{blob_id}` is missing in index files.", ) - .attach_context("blob id", id.to_string()) + .attach_context("blob_id", id.to_string()) .ask_report()); } } @@ -1085,14 +1085,14 @@ impl PrunePlan { Some(size) if size == pack.size => Ok(()), // size is ok => continue Some(size) => Err(RusticError::new( ErrorKind::Command, - "Pack size does not match the size in the index file.", + "Pack size `{size_in_pack_real}` of id `{pack_id}` does not match the expected size `{size_in_index_expected}` in the index file. ", ) - .attach_context("pack id", pack.id.to_string()) - .attach_context("size in index (expected)", pack.size.to_string()) - .attach_context("size in pack (real)", size.to_string()) + .attach_context("pack_id", pack.id.to_string()) + .attach_context("size_in_index_expected", pack.size.to_string()) + .attach_context("size_in_pack_real", size.to_string()) .ask_report()), - None => Err(RusticError::new(ErrorKind::Command, "Pack does not exist.") - .attach_context("pack id", pack.id.to_string()) + None => Err(RusticError::new(ErrorKind::Command, "Pack `{pack_id}` does not exist.") + .attach_context("pack_id", pack.id.to_string()) .ask_report()), } }; @@ -1101,9 +1101,9 @@ impl PrunePlan { PackToDo::Undecided => { return Err(RusticError::new( ErrorKind::Command, - "Pack got no decision what to do with it!", + "Pack `{pack_id}` got no decision what to do with it!", ) - .attach_context("pack id", pack.id.to_string()) + .attach_context("pack_id", pack.id.to_string()) .ask_report()); } PackToDo::Keep | PackToDo::Recover => { @@ -1354,9 +1354,9 @@ pub(crate) fn prune_repository( PackToDo::Undecided => { return Err(RusticError::new( ErrorKind::Command, - "Pack got no decision what to do with it!", + "Pack `{pack_id}` got no decision what to do with it!", ) - .attach_context("pack id", pack.id.to_string()) + .attach_context("pack_id", pack.id.to_string()) .ask_report()); } PackToDo::Keep => { diff --git a/crates/core/src/commands/repair/index.rs b/crates/core/src/commands/repair/index.rs index a869cf81..4ff7dc2f 100644 --- a/crates/core/src/commands/repair/index.rs +++ b/crates/core/src/commands/repair/index.rs @@ -81,13 +81,10 @@ pub(crate) fn repair_index( p.set_length(pack_read_header.len().try_into().map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to convert pack_read_header length to u64.", + "Failed to convert `pack_read_header` length `{length}` to u64.", err, ) - .attach_context( - "pack read header length", - pack_read_header.len().to_string(), - ) + .attach_context("length", pack_read_header.len().to_string()) })?); for (id, size_hint, packsize) in pack_read_header { debug!("reading pack {id}..."); @@ -199,13 +196,10 @@ pub(crate) fn index_checked_from_collector( p.set_length(pack_read_header.len().try_into().map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to convert pack_read_header length to u64.", + "Failed to convert `pack_read_header` length `{length}` to u64.", err, ) - .attach_context( - "pack read header length", - pack_read_header.len().to_string(), - ) + .attach_context("length", pack_read_header.len().to_string()) })?); let index_packs: Vec<_> = pack_read_header diff --git a/crates/core/src/commands/restore.rs b/crates/core/src/commands/restore.rs index 04b35f72..6d8e029f 100644 --- a/crates/core/src/commands/restore.rs +++ b/crates/core/src/commands/restore.rs @@ -223,7 +223,7 @@ pub(crate) fn collect_and_prepare( .map_err(|err| { RusticError::with_source( ErrorKind::InputOutput, - "Failed to create the directory. Please check the path and try again.", + "Failed to create the directory `{path}`. Please check the path and try again.", err ) .attach_context("path", path.display().to_string()) @@ -454,7 +454,7 @@ fn restore_contents( dest.set_length(path, *size).map_err(|err| { RusticError::with_source( ErrorKind::InputOutput, - "Failed to set the length of the file. Please check the path and try again.", + "Failed to set the length of the file `{path}`. Please check the path and try again.", err, ) .attach_context("path", path.display().to_string()) @@ -507,10 +507,10 @@ fn restore_contents( .map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to create the thread pool. Please try again.", + "Failed to create the thread pool with `{num_threads}` threads. Please try again.", err, ) - .attach_context("num threads", threads.to_string()) + .attach_context("num_threads", threads.to_string()) })?; pool.in_place_scope(|s| { @@ -679,7 +679,7 @@ impl RestorePlan { .map_err(|err| RusticError::with_source( ErrorKind::InputOutput, - "Failed to get the metadata of the file. Please check the path and try again.", + "Failed to get the metadata of the file `{path}`. Please check the path and try again.", err ) .attach_context("path", name.display().to_string()) @@ -700,7 +700,7 @@ impl RestorePlan { .map_err(|err| RusticError::with_source( ErrorKind::InputOutput, - "Failed to get the metadata of the file. Please check the path and try again.", + "Failed to get the metadata of the file `{path}`. Please check the path and try again.", err ) .attach_context("path", name.display().to_string()) @@ -736,7 +736,7 @@ impl RestorePlan { let usize_length = usize::try_from(length).map_err(|err| { RusticError::with_source( ErrorKind::Internal, - "Failed to convert the length to usize. Please try again.", + "Failed to convert the length `{length}` to usize. Please try again.", err, ) .attach_context("length", length.to_string()) diff --git a/crates/core/src/id.rs b/crates/core/src/id.rs index acd4a2b1..856cab24 100644 --- a/crates/core/src/id.rs +++ b/crates/core/src/id.rs @@ -88,7 +88,7 @@ impl FromStr for Id { hex::decode_to_slice(s, &mut id.0).map_err(|err| { RusticError::with_source( ErrorKind::InvalidInput, - "Failed to decode hex string into Id. The value must be a valid hexadecimal string.", + "Failed to decode hex string `{value}` into Id. The value must be a valid hexadecimal string.", err ) .attach_context("value", s) diff --git a/crates/core/src/index.rs b/crates/core/src/index.rs index 65d13940..4251d2ff 100644 --- a/crates/core/src/index.rs +++ b/crates/core/src/index.rs @@ -184,11 +184,12 @@ pub trait ReadIndex { ) -> RusticResult { self.get_id(tpe, id).map_or_else( || { - Err( - RusticError::new(ErrorKind::Internal, "Blob not found in index") - .attach_context("blob id", id.to_string()) - .attach_context("blob type", tpe.to_string()), + Err(RusticError::new( + ErrorKind::Internal, + "Blob `{id}` with type `{type}` not found in index", ) + .attach_context("id", id.to_string()) + .attach_context("type", tpe.to_string())) }, |ie| ie.read_data(be), ) diff --git a/crates/core/src/repofile/configfile.rs b/crates/core/src/repofile/configfile.rs index 3f157718..52e4d11b 100644 --- a/crates/core/src/repofile/configfile.rs +++ b/crates/core/src/repofile/configfile.rs @@ -140,7 +140,7 @@ impl ConfigFile { let chunker_poly = u64::from_str_radix(&self.chunker_polynomial, 16) .map_err(|err| RusticError::with_source( ErrorKind::InvalidInput, - "Parsing u64 from hex failed for polynomial, the value must be a valid hexadecimal string.", + "Parsing u64 from hex failed for polynomial `{polynomial}`, the value must be a valid hexadecimal string.", err) .attach_context("polynomial",self.chunker_polynomial.to_string())) ?; @@ -160,7 +160,7 @@ impl ConfigFile { (2, Some(c)) => Ok(Some(c)), _ => Err(RusticError::new( ErrorKind::Unsupported, - "Config version not supported. Please make sure, that you use the correct version.", + "Config version `{version}` not supported. Please make sure, that you use the correct version.", ) .attach_context("version", self.version.to_string())), } diff --git a/crates/core/src/repofile/keyfile.rs b/crates/core/src/repofile/keyfile.rs index c9aabe0c..ce134aa1 100644 --- a/crates/core/src/repofile/keyfile.rs +++ b/crates/core/src/repofile/keyfile.rs @@ -256,10 +256,10 @@ impl KeyFile { serde_json::from_slice(&data).map_err(|err| { RusticError::with_source( ErrorKind::Key, - "Couldn't deserialize the data for key.", + "Couldn't deserialize the data for key `{key_id}`.", err, ) - .attach_context("key id", id.to_string()) + .attach_context("key_id", id.to_string()) }) } } diff --git a/crates/core/src/repofile/packfile.rs b/crates/core/src/repofile/packfile.rs index b0577295..c0202ad6 100644 --- a/crates/core/src/repofile/packfile.rs +++ b/crates/core/src/repofile/packfile.rs @@ -285,12 +285,13 @@ impl PackHeader { trace!("header size: {size_real}"); if size_real + constants::LENGTH_LEN > pack_size { - return Err( - RusticError::new(ErrorKind::Internal, "Read header length is too large!") - .attach_context("size real", size_real.to_string()) - .attach_context("pack size", pack_size.to_string()) - .attach_context("length field value", constants::LENGTH_LEN.to_string()), - ); + return Err(RusticError::new( + ErrorKind::Internal, + "Read header length `{size_real}` + `{length}` is larger than `{pack_size}`!", + ) + .attach_context("size_real", size_real.to_string()) + .attach_context("pack_size", pack_size.to_string()) + .attach_context("length", constants::LENGTH_LEN.to_string())); } // now read the header @@ -312,17 +313,17 @@ impl PackHeader { ErrorKind::Internal, "Read header length doesn't match header contents!", ) - .attach_context("size real", size_real.to_string()) - .attach_context("size computed", header.size().to_string())); + .attach_context("size_real", size_real.to_string()) + .attach_context("size_computed", header.size().to_string())); } if header.pack_size() != pack_size { return Err(RusticError::new( ErrorKind::Internal, - "pack size computed from header doesn't match real pack file size!", + "pack size `{size_computed}` computed from header doesn't match real pack file size `{size_real}`!", ) - .attach_context("size real", pack_size.to_string()) - .attach_context("size computed", header.pack_size().to_string())); + .attach_context("size_real", pack_size.to_string()) + .attach_context("size_computed", header.pack_size().to_string())); } Ok(header) diff --git a/crates/core/src/repofile/snapshotfile.rs b/crates/core/src/repofile/snapshotfile.rs index bc8489cb..ec7eecc6 100644 --- a/crates/core/src/repofile/snapshotfile.rs +++ b/crates/core/src/repofile/snapshotfile.rs @@ -136,7 +136,7 @@ impl SnapshotOptions { self.tags.push(StringList::from_str(tag).map_err(|err| { RusticError::with_source( ErrorKind::InvalidInput, - "Failed to create string list from tag. The value must be a valid unicode string.", + "Failed to create string list from tag `{tag}`. The value must be a valid unicode string.", err, ) .attach_context("tag", tag) @@ -390,7 +390,7 @@ impl SnapshotFile { .ok_or_else(|| { RusticError::new( ErrorKind::InvalidInput, - "Failed to convert hostname to string. The value must be a valid unicode string.", + "Failed to convert hostname `{hostname}` to string. The value must be a valid unicode string.", ) .attach_context("hostname", hostname.to_string_lossy().to_string()) })? @@ -405,7 +405,7 @@ impl SnapshotFile { time + Duration::from_std(*duration).map_err(|err| { RusticError::with_source( ErrorKind::InvalidInput, - "Failed to convert duration to std::time::Duration. Please make sure the value is a valid duration string.", + "Failed to convert duration `{duration}` to std::time::Duration. Please make sure the value is a valid duration string.", err, ) .attach_context("duration", duration.to_string()) @@ -442,7 +442,7 @@ impl SnapshotFile { snap.description = Some(std::fs::read_to_string(path).map_err(|err| { RusticError::with_source( ErrorKind::InvalidInput, - "Failed to read description file. Please make sure the file exists and is readable.", + "Failed to read description file `{path}`. Please make sure the file exists and is readable.", err, ) .attach_context("path", path.to_string_lossy().to_string()) diff --git a/crates/core/src/repository.rs b/crates/core/src/repository.rs index 98545529..bc9b4959 100644 --- a/crates/core/src/repository.rs +++ b/crates/core/src/repository.rs @@ -185,7 +185,7 @@ impl RepositoryOptions { let mut file = BufReader::new(File::open(file).map_err(|err| { RusticError::with_source( ErrorKind::Password, - "Opening password file failed. Is the path correct?", + "Opening password file failed. Is the path `{path}` correct?", err, ) .attach_context("path", file.display().to_string()) @@ -205,7 +205,7 @@ impl RepositoryOptions { error!("password-command could not be executed: {}", err); return Err(RusticError::with_source( ErrorKind::Password, - "Password command could not be executed.", + "Password command `{command}` could not be executed", err, ) .attach_context("command", command.to_string())); @@ -215,10 +215,10 @@ impl RepositoryOptions { let output = match process.wait_with_output() { Ok(output) => output, Err(err) => { - error!("error reading output from password-command: {}", err); + error!("error reading output from password-command: {err}"); return Err(RusticError::with_source( ErrorKind::Password, - "Error reading output from password command.", + "Error reading output from password command `{command}`", err, ) .attach_context("command", command.to_string())); @@ -234,7 +234,7 @@ impl RepositoryOptions { error!("password-command {s}"); return Err(RusticError::new( ErrorKind::Password, - "Password command did not exit successfully.", + "Password command `{command}` did not exit successfully: `{status}`", ) .attach_context("command", command.to_string()) .attach_context("status", s)); @@ -360,7 +360,7 @@ impl

Repository { if warm_up.args().iter().all(|c| !c.contains("%id")) { return Err(RusticError::new( ErrorKind::Command, - "No `%id` specified in warm-up command. Please specify `%id` in the command.", + "No `%id` specified in warm-up command `{command}`. Please specify `%id` in the command.", ) .attach_context("command", warm_up.to_string())); } @@ -425,7 +425,7 @@ impl Repository { 0 => Ok(None), _ => Err(RusticError::new( ErrorKind::Configuration, - "More than one repository found. Please check the config file.", + "More than one repository found for `{name}`. Please check the config file.", ) .attach_context("name", self.name.clone())), } @@ -484,7 +484,7 @@ impl Repository { let config_id = self.config_id()?.ok_or_else(|| { RusticError::new( ErrorKind::Configuration, - "No repository config file found. Please check the repository.", + "No repository config file found for `{name}`. Please check the repository.", ) .attach_context("name", self.name.clone()) })?; @@ -497,7 +497,7 @@ impl Repository { if keys != hot_keys { return Err(RusticError::new( ErrorKind::Key, - "Keys of hot and cold repositories don't match. Please check the keys.", + "Keys of hot and cold repositories don't match for `{name}`. Please check the keys.", ) .attach_context("name", self.name.clone())); } @@ -541,7 +541,7 @@ impl Repository { let password = self.password()?.ok_or_else(|| { RusticError::new( ErrorKind::Password, - "No password given, or Password was empty. Please specify a valid password.", + "No password given, or Password was empty. Please specify a valid password for `{name}`.", ) .attach_context("name", self.name.clone()) })?; @@ -577,7 +577,7 @@ impl Repository { if self.config_id()?.is_some() { return Err(RusticError::new( ErrorKind::Configuration, - "Config file already exists. Please check the repository.", + "Config file already exists for `{name}`. Please check the repository.", ) .attach_context("name", self.name)); } @@ -1567,9 +1567,9 @@ impl Repository { let ie = self.index().get_id(T::TYPE, &blob_id).ok_or_else(|| { RusticError::new( ErrorKind::Internal, - "BlobID not found in index, but should be there.", + "Blob ID `{id}` not found in index, but should be there.", ) - .attach_context("blob id", blob_id.to_string()) + .attach_context("id", blob_id.to_string()) .ask_report() })?; diff --git a/crates/core/src/repository/warm_up.rs b/crates/core/src/repository/warm_up.rs index 5621570b..22131a92 100644 --- a/crates/core/src/repository/warm_up.rs +++ b/crates/core/src/repository/warm_up.rs @@ -98,7 +98,7 @@ fn warm_up_command( .map_err(|err| { RusticError::with_source( ErrorKind::ExternalCommand, - "Error in executing warm-up command.", + "Error in executing warm-up command `{command}`.", err, ) .attach_context("command", command.to_string()) diff --git a/crates/core/src/vfs.rs b/crates/core/src/vfs.rs index cf7056a3..9a1c9d1c 100644 --- a/crates/core/src/vfs.rs +++ b/crates/core/src/vfs.rs @@ -273,7 +273,7 @@ impl Vfs { .map_err(|err| { RusticError::with_source( ErrorKind::Vfs, - "Failed to add a link to root tree.", + "Failed to add a link `{name}` to root tree at `{path}`", err, ) .attach_context("path", path.display().to_string()) @@ -286,11 +286,11 @@ impl Vfs { .map_err(|err| { RusticError::with_source( ErrorKind::Vfs, - "Failed to add repository tree to root tree.", + "Failed to add repository tree `{tree_id}` to root tree at `{path}`", err, ) .attach_context("path", path.display().to_string()) - .attach_context("tree id", snap.tree.to_string()) + .attach_context("tree_id", snap.tree.to_string()) .ask_report() })?; } @@ -311,12 +311,12 @@ impl Vfs { .map_err(|err| { RusticError::with_source( ErrorKind::Vfs, - "Failed to link latest entries to root tree.", + "Failed to link latest `{target}` entry to root tree at `{path}`", err, ) - .attach_context("latest", "link") .attach_context("path", path.display().to_string()) .attach_context("target", target.to_string_lossy()) + .attach_context("latest", "link") .ask_report() })?; } @@ -330,12 +330,12 @@ impl Vfs { .map_err(|err| { RusticError::with_source( ErrorKind::Vfs, - "Failed to add latest subtree to root tree.", + "Failed to add latest subtree id `{id}` to root tree at `{path}`", err, ) - .attach_context("latest", "dir") .attach_context("path", path.display().to_string()) - .attach_context("tree id", subtree.to_string()) + .attach_context("tree_id", subtree.to_string()) + .attach_context("latest", "dir") .ask_report() })?; } @@ -368,9 +368,13 @@ impl Vfs { ) -> RusticResult { let meta = Metadata::default(); match self.tree.get_path(path).map_err(|err| { - RusticError::with_source(ErrorKind::Vfs, "Failed to get tree at given path.", err) - .attach_context("path", path.display().to_string()) - .ask_report() + RusticError::with_source( + ErrorKind::Vfs, + "Failed to get tree at given path `{path}`", + err, + ) + .attach_context("path", path.display().to_string()) + .ask_report() })? { VfsPath::RusticPath(tree_id, path) => Ok(repo.node_from_path(*tree_id, &path)?), VfsPath::VirtualTree(_) => { @@ -414,9 +418,13 @@ impl Vfs { path: &Path, ) -> RusticResult> { let result = match self.tree.get_path(path).map_err(|err| { - RusticError::with_source(ErrorKind::Vfs, "Failed to get tree at given path.", err) - .attach_context("path", path.display().to_string()) - .ask_report() + RusticError::with_source( + ErrorKind::Vfs, + "Failed to get tree at given path `{path}`", + err, + ) + .attach_context("path", path.display().to_string()) + .ask_report() })? { VfsPath::RusticPath(tree_id, path) => { let node = repo.node_from_path(*tree_id, &path)?; @@ -440,7 +448,7 @@ impl Vfs { VfsPath::Link(str) => { return Err(RusticError::new( ErrorKind::Vfs, - "No directory entries for symlink found. Is the path valid unicode?", + "No directory entries for symlink `{symlink}` found. Is the path valid unicode?", ) .attach_context("symlink", str.to_string_lossy().to_string())); } diff --git a/crates/testing/src/backend.rs b/crates/testing/src/backend.rs index 26dbc4fd..418bd320 100644 --- a/crates/testing/src/backend.rs +++ b/crates/testing/src/backend.rs @@ -73,8 +73,10 @@ pub mod in_memory_backend { buf: Bytes, ) -> RusticResult<()> { if self.0.write().unwrap()[tpe].insert(*id, buf).is_some() { - return Err(RusticError::new(ErrorKind::Backend, "Id already exists.") - .attach_context("id", id.to_string())); + return Err( + RusticError::new(ErrorKind::Backend, "ID `{id}` already exists.") + .attach_context("id", id.to_string()), + ); } Ok(()) @@ -82,8 +84,10 @@ pub mod in_memory_backend { fn remove(&self, tpe: FileType, id: &Id, _cacheable: bool) -> RusticResult<()> { if self.0.write().unwrap()[tpe].remove(id).is_none() { - return Err(RusticError::new(ErrorKind::Backend, "Id does not exist.") - .attach_context("id", id.to_string())); + return Err( + RusticError::new(ErrorKind::Backend, "ID `{id}` does not exist.") + .attach_context("id", id.to_string()), + ); } Ok(()) } From b98e35b7a322de3ec64cbead85fdc8627918d81d Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Wed, 30 Oct 2024 23:01:11 +0100 Subject: [PATCH 119/129] attach more context to errors that are already RusticErrors Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/choose.rs | 11 ++++------- crates/core/src/archiver/file_archiver.rs | 8 +++----- crates/core/src/blob/packer.rs | 21 ++++++++++----------- crates/core/src/repository.rs | 5 +---- crates/core/src/vfs/webdavfs.rs | 4 ++-- 5 files changed, 20 insertions(+), 29 deletions(-) diff --git a/crates/backend/src/choose.rs b/crates/backend/src/choose.rs index 1eee995f..9dca3831 100644 --- a/crates/backend/src/choose.rs +++ b/crates/backend/src/choose.rs @@ -117,13 +117,10 @@ impl BackendOptions { be_type .to_backend(location.clone(), options.into()) .map_err(|err| { - RusticError::with_source( - ErrorKind::Backend, - "Could not load the backend `{name}` at `{location}`. Please check the given backend and try again.", - err, - ) - .attach_context("name", be_type.to_string()) - .attach_context("location", location.to_string()) + err + .prepend_guidance_line("Could not load the backend `{name}` at `{location}`. Please check the given backend and try again.") + .attach_context("name", be_type.to_string()) + .attach_context("location", location.to_string()) }) }) .transpose() diff --git a/crates/core/src/archiver/file_archiver.rs b/crates/core/src/archiver/file_archiver.rs index 33a614bb..07eb4b7e 100644 --- a/crates/core/src/archiver/file_archiver.rs +++ b/crates/core/src/archiver/file_archiver.rs @@ -125,11 +125,9 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> FileArchiver<'a, BE, I> { )? .open() .map_err(|err| { - RusticError::with_source( - ErrorKind::InputOutput, - "Failed to open ReadSourceOpen at `{path}`", - err, - ) + err + .overwrite_kind(ErrorKind::InputOutput) + .prepend_guidance_line("Failed to open ReadSourceOpen at `{path}`") .attach_context("path", path.display().to_string()) })?; diff --git a/crates/core/src/blob/packer.rs b/crates/core/src/blob/packer.rs index 33a26ed4..a02b57c4 100644 --- a/crates/core/src/blob/packer.rs +++ b/crates/core/src/blob/packer.rs @@ -513,12 +513,9 @@ impl RawPacker { /// * If the packfile could not be saved fn finalize(&mut self) -> RusticResult { self.save().map_err(|err| { - RusticError::with_source( - ErrorKind::Internal, - "Failed to save packfile. Data may be lost.", - err, - ) - .ask_report() + err.overwrite_kind(ErrorKind::Internal) + .prepend_guidance_line("Failed to save packfile. Data may be lost.") + .ask_report() })?; self.file_writer.take().unwrap().finalize()?; @@ -920,6 +917,7 @@ impl Repacker { blob.offset, blob.length, )?; + self.packer .add_raw( &data, @@ -929,12 +927,13 @@ impl Repacker { Some(self.size_limit), ) .map_err(|err| { - RusticError::with_source( - ErrorKind::Internal, - "Failed to fast-add (unchecked) blob to packfile.", - err, - ) + err.overwrite_kind(ErrorKind::Internal) + .prepend_guidance_line( + "Failed to fast-add (unchecked) blob `{blob_id}` to packfile.", + ) + .attach_context("blob_id", blob.id.to_string()) })?; + Ok(()) } diff --git a/crates/core/src/repository.rs b/crates/core/src/repository.rs index bc9b4959..5c76c713 100644 --- a/crates/core/src/repository.rs +++ b/crates/core/src/repository.rs @@ -714,7 +714,6 @@ impl Repository { /// The result of the warm up pub fn warm_up(&self, packs: impl ExactSizeIterator) -> RusticResult<()> { warm_up(self, packs) - .map_err(|err| RusticError::with_source(ErrorKind::Command, "Warm-up failed.", err)) } /// Warm up the given pack files and wait the configured waiting time. @@ -728,9 +727,7 @@ impl Repository { /// * If the command could not be parsed. /// * If the thread pool could not be created. pub fn warm_up_wait(&self, packs: impl ExactSizeIterator) -> RusticResult<()> { - warm_up_wait(self, packs).map_err(|err| { - RusticError::with_source(ErrorKind::Command, "Warm-up with waiting time failed.", err) - }) + warm_up_wait(self, packs) } } diff --git a/crates/core/src/vfs/webdavfs.rs b/crates/core/src/vfs/webdavfs.rs index 8495162c..c2401136 100644 --- a/crates/core/src/vfs/webdavfs.rs +++ b/crates/core/src/vfs/webdavfs.rs @@ -186,7 +186,7 @@ impl = Box::new(DavFsFile { node, open, @@ -274,7 +274,7 @@ impl DavFile for D .fs .repo .read_file_at(&self.open, self.seek, count) - .map_err(|_| FsError::GeneralFailure)?; + .map_err(|_err| FsError::GeneralFailure)?; self.seek += data.len(); Ok(data) } From 17180a6899f86930823cb9da08c63b5ed947f2e1 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Wed, 30 Oct 2024 23:06:40 +0100 Subject: [PATCH 120/129] add instructions about forwarding RusticError in a `map_err` Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/error.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index efb0d296..cf5ae6f1 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -11,7 +11,7 @@ //! `pub(crate) fn` visibility should use a local error and thus a Result and error type limited in visibility, e.g. //! `pub(crate) type ArchiverResult = Result`. //! -//! ### Downgrading +//! ### Downgrading and Forwarding //! //! `RusticError`s should **not** be downgraded, instead we **upgrade** the function signature to contain a `RusticResult`. //! For instance, if a function returns `Result` and we discover an error path that contains a `RusticError`, @@ -19,6 +19,11 @@ //! `Result (==RusticResult)` or nested results like `RusticResult>`. //! So even if the visibility of that function is `fn` or `pub(crate) fn` it should return a `RusticResult` containing a `RusticError`. //! +//! If we `map_err` or `and_then` a `RusticError`, we don't want to create a new RusticError from it, but just attach some context +//! to it, e.g. `map_err(|e| e.attach_context("key", "value"))`, so we don't lose the original error. We can also change the error +//! kind with `map_err(|e| e.overwrite_kind(ErrorKind::NewKind))`. If we want to pre- or append to the guidance, we can use +//! `map_err(|e| e.append_guidance_line("new line"))` or `map_err(|e| e.prepend_guidance_line("new line"))`. +//! //! ### Conversion and Nested Results //! //! Converting between different error kinds or their variants e.g. `TreeErrorKind::Channel` -> `ArchiverErrorKind::Channel` From 125e7e2b179c2619c20e09d0f466950aaab2831d Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Fri, 1 Nov 2024 17:40:29 +0100 Subject: [PATCH 121/129] update error display Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/error.rs | 11 +++++++++-- .../core/tests/snapshots/errors__error_display.snap | 6 ++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index cf5ae6f1..004ec335 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -56,7 +56,7 @@ use derive_more::derive::Display; use smol_str::SmolStr; use std::{ - backtrace::Backtrace, + backtrace::{Backtrace, BacktraceStatus}, borrow::Cow, convert::Into, fmt::{self, Display}, @@ -265,7 +265,7 @@ impl Display for RusticError { writeln!(f, "\nContext:")?; self.context .iter() - .try_for_each(|(key, value)| writeln!(f, "- {key}:\t{value}"))?; + .try_for_each(|(key, value)| writeln!(f, "- {key}: {value}"))?; } if let Some(cause) = &self.source { @@ -284,6 +284,13 @@ impl Display for RusticError { if let Some(backtrace) = &self.backtrace { writeln!(f, "\nBacktrace:")?; writeln!(f, "{backtrace}")?; + + if backtrace.status() == BacktraceStatus::Disabled { + writeln!( + f, + "\nTo enable backtraces, set the RUST_BACKTRACE=\"1\" environment variable." + )?; + } } Ok(()) diff --git a/crates/core/tests/snapshots/errors__error_display.snap b/crates/core/tests/snapshots/errors__error_display.snap index 598bd9eb..b9be299d 100644 --- a/crates/core/tests/snapshots/errors__error_display.snap +++ b/crates/core/tests/snapshots/errors__error_display.snap @@ -27,8 +27,8 @@ Thank you for helping us improve rustic! Some additional details ... Context: -- path: /path/to/file -- called: used s3 backend +- path: /path/to/file +- called: used s3 backend Caused by: Networking Error : (source: None) @@ -39,3 +39,5 @@ Status: Permanent Backtrace: disabled backtrace + +To enable backtraces, set the RUST_BACKTRACE="1" environment variable. From 8baaf40e06a238f8f6008c7502aacbfc6abf2d34 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Tue, 5 Nov 2024 17:50:45 +0100 Subject: [PATCH 122/129] refactor: use `ecow` instead of `smol_str` due to recommendation See https://github.com/rosetta-rs/string-rosetta-rs Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- Cargo.lock | 27 +++++------------- crates/core/Cargo.toml | 2 +- crates/core/src/error.rs | 59 ++++++++++++++++++++-------------------- 3 files changed, 37 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2bcbf619..47d2281f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -471,15 +471,6 @@ dependencies = [ "piper", ] -[[package]] -name = "borsh" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" -dependencies = [ - "cfg_aliases", -] - [[package]] name = "bstr" version = "1.10.0" @@ -1169,6 +1160,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "ecow" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42fc0a93992b20c58b99e59d61eaf1635a25bfbe49e4275c34ba0aee98119ba" + [[package]] name = "either" version = "1.13.0" @@ -3346,6 +3343,7 @@ dependencies = [ "dirs", "displaydoc", "dunce", + "ecow", "enum-map", "enum-map-derive", "enumset", @@ -3387,7 +3385,6 @@ dependencies = [ "sha2", "shell-words", "simplelog", - "smol_str", "strum", "tar", "tempfile", @@ -3822,16 +3819,6 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" -[[package]] -name = "smol_str" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9676b89cd56310a87b93dec47b11af744f34d5fc9f367b829474eec0a891350d" -dependencies = [ - "borsh", - "serde", -] - [[package]] name = "socket2" version = "0.5.7" diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 2605182b..0c16f44e 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -98,6 +98,7 @@ runtime-format = "0.1.3" bytes = { workspace = true } bytesize = "1.3.0" chrono = { version = "0.4.38", default-features = false, features = ["clock", "serde"] } +ecow = "0.2.3" enum-map = { workspace = true } enum-map-derive = "0.17.0" enumset = { version = "1.1.5", features = ["serde"] } @@ -106,7 +107,6 @@ humantime = "2.1.0" itertools = "0.13.0" quick_cache = "0.6.9" shell-words = "1.1.0" -smol_str = "0.3.2" strum = { version = "0.26.3", features = ["derive"] } zstd = "0.13.2" diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 004ec335..296c31c5 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -54,10 +54,9 @@ // use std::fmt; use derive_more::derive::Display; -use smol_str::SmolStr; +use ecow::{EcoString, EcoVec}; use std::{ backtrace::{Backtrace, BacktraceStatus}, - borrow::Cow, convert::Into, fmt::{self, Display}, }; @@ -160,25 +159,25 @@ pub struct RusticError { kind: ErrorKind, /// The error message with guidance. - guidance: SmolStr, + guidance: EcoString, /// The URL of the documentation for the error. - docs_url: Option, + docs_url: Option, /// Error code. - error_code: Option, + error_code: Option, /// Whether to ask the user to report the error. ask_report: bool, /// The URL of an already existing issue that is related to this error. - existing_issue_urls: Cow<'static, [SmolStr]>, + existing_issue_urls: EcoVec, /// The URL of the issue tracker for opening a new issue. - new_issue_url: Option, + new_issue_url: Option, /// The context of the error. - context: Cow<'static, [(&'static str, SmolStr)]>, + context: EcoVec<(EcoString, EcoString)>, /// Chain to the cause of the error. source: Option>, @@ -219,7 +218,7 @@ impl Display for RusticError { } if let Some(code) = &self.error_code { - let default_docs_url = SmolStr::from(constants::DEFAULT_DOCS_URL); + let default_docs_url = EcoString::from(constants::DEFAULT_DOCS_URL); let docs_url = self .docs_url .as_ref() @@ -244,7 +243,7 @@ impl Display for RusticError { } if self.ask_report { - let default_issue_url = SmolStr::from(constants::DEFAULT_ISSUE_URL); + let default_issue_url = EcoString::from(constants::DEFAULT_ISSUE_URL); let new_issue_url = self.new_issue_url.as_ref().unwrap_or(&default_issue_url); writeln!( @@ -300,16 +299,16 @@ impl Display for RusticError { // Accessors for anything we do want to expose publicly. impl RusticError { /// Creates a new error with the given kind and guidance. - pub fn new(kind: ErrorKind, guidance: impl Into) -> Box { + pub fn new(kind: ErrorKind, guidance: impl Into) -> Box { Box::new(Self { kind, guidance: guidance.into(), - context: Cow::default(), + context: EcoVec::default(), source: None, error_code: None, docs_url: None, new_issue_url: None, - existing_issue_urls: Cow::default(), + existing_issue_urls: EcoVec::default(), severity: None, status: None, ask_report: false, @@ -322,7 +321,7 @@ impl RusticError { /// Creates a new error with the given kind and guidance. pub fn with_source( kind: ErrorKind, - guidance: impl Into, + guidance: impl Into, source: impl Into>, ) -> Box { Self::new(kind, guidance).attach_source(source) @@ -386,7 +385,7 @@ impl RusticError { } /// Attach the error message with guidance. - pub fn overwrite_guidance(self, value: impl Into) -> Box { + pub fn overwrite_guidance(self, value: impl Into) -> Box { Box::new(Self { guidance: value.into(), ..self @@ -395,7 +394,7 @@ impl RusticError { /// Append a newline to the guidance message. /// This is useful for adding additional information to the guidance. - pub fn append_guidance_line(self, value: impl Into) -> Box { + pub fn append_guidance_line(self, value: impl Into) -> Box { Box::new(Self { guidance: format!("{}\n{}", self.guidance, value.into()).into(), ..self @@ -404,7 +403,7 @@ impl RusticError { /// Prepend a newline to the guidance message. /// This is useful for adding additional information to the guidance. - pub fn prepend_guidance_line(self, value: impl Into) -> Box { + pub fn prepend_guidance_line(self, value: impl Into) -> Box { Box::new(Self { guidance: format!("{}\n{}", value.into(), self.guidance).into(), ..self @@ -413,10 +412,12 @@ impl RusticError { // IMPORTANT: This is manually implemented to allow for multiple contexts to be added. /// Attach context to the error. - pub fn attach_context(mut self, key: &'static str, value: impl Into) -> Box { - let mut context = self.context.into_owned(); - context.push((key, value.into())); - self.context = Cow::from(context); + pub fn attach_context( + mut self, + key: impl Into, + value: impl Into, + ) -> Box { + self.context.push((key.into(), value.into())); Box::new(self) } @@ -426,15 +427,15 @@ impl RusticError { /// /// This should not be used in most cases, as it will overwrite any existing contexts. /// Rather use `attach_context` for multiple contexts. - pub fn overwrite_context(self, value: impl Into>) -> Box { + pub fn overwrite_context(self, value: impl Into>) -> Box { Box::new(Self { - context: Cow::from(value.into()), + context: EcoVec::from(value.into()), ..self }) } /// Attach the URL of the documentation for the error. - pub fn attach_docs_url(self, value: impl Into) -> Box { + pub fn attach_docs_url(self, value: impl Into) -> Box { Box::new(Self { docs_url: Some(value.into()), ..self @@ -442,7 +443,7 @@ impl RusticError { } /// Attach an error code. - pub fn attach_error_code(self, value: impl Into) -> Box { + pub fn attach_error_code(self, value: impl Into) -> Box { Box::new(Self { error_code: Some(value.into()), ..self @@ -450,7 +451,7 @@ impl RusticError { } /// Attach the URL of the issue tracker for opening a new issue. - pub fn attach_new_issue_url(self, value: impl Into) -> Box { + pub fn attach_new_issue_url(self, value: impl Into) -> Box { Box::new(Self { new_issue_url: Some(value.into()), ..self @@ -458,10 +459,8 @@ impl RusticError { } /// Attach the URL of an already existing issue that is related to this error. - pub fn attach_existing_issue_url(mut self, value: impl Into) -> Box { - let mut issue_urls = self.existing_issue_urls.into_owned(); - issue_urls.push(value.into()); - self.existing_issue_urls = Cow::from(issue_urls); + pub fn attach_existing_issue_url(mut self, value: impl Into) -> Box { + self.existing_issue_urls.push(value.into()); Box::new(self) } From 04feb7aca4ab5daea4d9e112244403be47184767 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Tue, 5 Nov 2024 17:54:26 +0100 Subject: [PATCH 123/129] Remove `Command` variant from ErrorKind. Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/backend/src/local.rs | 4 ++-- crates/core/src/commands/cat.rs | 2 +- crates/core/src/commands/check.rs | 4 ++-- crates/core/src/commands/prune.rs | 10 +++++----- crates/core/src/error.rs | 2 -- crates/core/src/repository.rs | 2 +- 6 files changed, 11 insertions(+), 13 deletions(-) diff --git a/crates/backend/src/local.rs b/crates/backend/src/local.rs index 82685915..02cf6c5d 100644 --- a/crates/backend/src/local.rs +++ b/crates/backend/src/local.rs @@ -149,7 +149,7 @@ impl LocalBackend { .status() .map_err(|err| { RusticError::with_source( - ErrorKind::Command, + ErrorKind::ExternalCommand, "Failed to execute `{command}`. Please check the command and try again.", err, ) @@ -158,7 +158,7 @@ impl LocalBackend { if !status.success() { return Err(RusticError::new( - ErrorKind::Command, + ErrorKind::ExternalCommand, "Command was not successful: `{command}` failed with status `{status}`.", ) .attach_context("command", command.to_string()) diff --git a/crates/core/src/commands/cat.rs b/crates/core/src/commands/cat.rs index ae4a8cb1..55caa248 100644 --- a/crates/core/src/commands/cat.rs +++ b/crates/core/src/commands/cat.rs @@ -106,7 +106,7 @@ pub(crate) fn cat_tree( let node = Tree::node_from_path(repo.dbe(), repo.index(), snap.tree, Path::new(path))?; let id = node.subtree.ok_or_else(|| { RusticError::new( - ErrorKind::Command, + ErrorKind::InvalidInput, "Path `{path}` in Node subtree is not a directory. Please provide a directory path.", ) .attach_context("path", path.to_string()) diff --git a/crates/core/src/commands/check.rs b/crates/core/src/commands/check.rs index 242bea9b..ca92a12c 100644 --- a/crates/core/src/commands/check.rs +++ b/crates/core/src/commands/check.rs @@ -690,7 +690,7 @@ fn check_pack( let pack_header_len = PackHeaderLength::from_binary(&data.split_off(data.len() - 4)) .map_err(|err| { RusticError::with_source( - ErrorKind::Command, + ErrorKind::Internal, "Error reading pack header length `{length}` for `{pack_id}`", err, ) @@ -710,7 +710,7 @@ fn check_pack( let pack_blobs = PackHeader::from_binary(&header) .map_err(|err| { RusticError::with_source( - ErrorKind::Command, + ErrorKind::Internal, "Error reading pack header for id `{pack_id}`", err, ) diff --git a/crates/core/src/commands/prune.rs b/crates/core/src/commands/prune.rs index 56ee7a73..d1225eb3 100644 --- a/crates/core/src/commands/prune.rs +++ b/crates/core/src/commands/prune.rs @@ -813,7 +813,7 @@ impl PrunePlan { for (id, count) in &self.used_ids { if *count == 0 { return Err(RusticError::new( - ErrorKind::Command, + ErrorKind::Internal, "Blob ID `{blob_id}` is missing in index files.", ) .attach_context("blob_id", id.to_string()) @@ -1084,14 +1084,14 @@ impl PrunePlan { match existing_size { Some(size) if size == pack.size => Ok(()), // size is ok => continue Some(size) => Err(RusticError::new( - ErrorKind::Command, + ErrorKind::Internal, "Pack size `{size_in_pack_real}` of id `{pack_id}` does not match the expected size `{size_in_index_expected}` in the index file. ", ) .attach_context("pack_id", pack.id.to_string()) .attach_context("size_in_index_expected", pack.size.to_string()) .attach_context("size_in_pack_real", size.to_string()) .ask_report()), - None => Err(RusticError::new(ErrorKind::Command, "Pack `{pack_id}` does not exist.") + None => Err(RusticError::new(ErrorKind::Internal, "Pack `{pack_id}` does not exist.") .attach_context("pack_id", pack.id.to_string()) .ask_report()), } @@ -1100,7 +1100,7 @@ impl PrunePlan { match pack.to_do { PackToDo::Undecided => { return Err(RusticError::new( - ErrorKind::Command, + ErrorKind::Internal, "Pack `{pack_id}` got no decision what to do with it!", ) .attach_context("pack_id", pack.id.to_string()) @@ -1353,7 +1353,7 @@ pub(crate) fn prune_repository( match pack.to_do { PackToDo::Undecided => { return Err(RusticError::new( - ErrorKind::Command, + ErrorKind::Internal, "Pack `{pack_id}` got no decision what to do with it!", ) .attach_context("pack_id", pack.id.to_string()) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 296c31c5..c6e6c170 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -115,8 +115,6 @@ pub enum ErrorKind { AppendOnly, /// the backend Backend, - /// the a command - Command, /// the configuration Configuration, /// cryptographic operations diff --git a/crates/core/src/repository.rs b/crates/core/src/repository.rs index 5c76c713..a94f108b 100644 --- a/crates/core/src/repository.rs +++ b/crates/core/src/repository.rs @@ -359,7 +359,7 @@ impl

Repository { if let Some(warm_up) = &opts.warm_up_command { if warm_up.args().iter().all(|c| !c.contains("%id")) { return Err(RusticError::new( - ErrorKind::Command, + ErrorKind::MissingInput, "No `%id` specified in warm-up command `{command}`. Please specify `%id` in the command.", ) .attach_context("command", warm_up.to_string())); From ed4c005c71d16e6e30754fd30c28db8e30356a27 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Tue, 5 Nov 2024 19:26:36 +0100 Subject: [PATCH 124/129] fix(clippy): lints Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/error.rs | 2 +- crates/core/src/vfs.rs | 16 +++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index c6e6c170..7eeb557f 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -427,7 +427,7 @@ impl RusticError { /// Rather use `attach_context` for multiple contexts. pub fn overwrite_context(self, value: impl Into>) -> Box { Box::new(Self { - context: EcoVec::from(value.into()), + context: value.into(), ..self }) } diff --git a/crates/core/src/vfs.rs b/crates/core/src/vfs.rs index 9a1c9d1c..5904ea4d 100644 --- a/crates/core/src/vfs.rs +++ b/crates/core/src/vfs.rs @@ -380,15 +380,13 @@ impl Vfs { VfsPath::VirtualTree(_) => { Ok(Node::new(String::new(), NodeType::Dir, meta, None, None)) } - VfsPath::Link(target) => { - return Ok(Node::new( - String::new(), - NodeType::from_link(Path::new(target)), - meta, - None, - None, - )); - } + VfsPath::Link(target) => Ok(Node::new( + String::new(), + NodeType::from_link(Path::new(target)), + meta, + None, + None, + )), } } From 7a910abea8cf7dc5759cd45287252352bc2c0712 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 7 Nov 2024 02:36:05 +0100 Subject: [PATCH 125/129] upgrade thiserror to 2.0.0 Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- Cargo.lock | 116 ++++++++++++++++++++++---------------- Cargo.toml | 11 +++- crates/backend/Cargo.toml | 8 +-- crates/core/Cargo.toml | 6 +- 4 files changed, 83 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 47d2281f..3d18f3db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -291,7 +291,7 @@ checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -521,7 +521,7 @@ dependencies = [ "cached_proc_macro_types", "hashbrown 0.14.5", "once_cell", - "thiserror", + "thiserror 1.0.64", "web-time", ] @@ -534,7 +534,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -568,7 +568,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03fa8484a7f2eef80e6dd2e2be90b322b9c29aeb1bbc206013d6eb2104db7241" dependencies = [ "serde", - "thiserror", + "thiserror 1.0.64", "toml", ] @@ -592,7 +592,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -693,7 +693,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -756,7 +756,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -951,7 +951,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -962,7 +962,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1047,7 +1047,7 @@ checksum = "64b697ac90ff296f0fc031ee5a61c7ac31fb9fff50e3fb32873b09223613fc0c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1067,7 +1067,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", "unicode-xid", ] @@ -1080,7 +1080,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1130,7 +1130,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1195,7 +1195,7 @@ checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1217,7 +1217,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1426,7 +1426,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1979,7 +1979,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -2139,7 +2139,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -2161,7 +2161,7 @@ dependencies = [ "rustc_version", "smallvec", "tagptr", - "thiserror", + "thiserror 1.0.64", "triomphe", "uuid", ] @@ -2355,7 +2355,7 @@ dependencies = [ "once_cell", "shell-escape", "tempfile", - "thiserror", + "thiserror 1.0.64", "tokio", ] @@ -2407,7 +2407,7 @@ dependencies = [ "openssh", "openssh-sftp-protocol-error", "ssh_format_error", - "thiserror", + "thiserror 1.0.64", "tokio", ] @@ -2433,7 +2433,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0719269eb3f037866ae07ec89cb44ed2c1d63b72b2390cef8e1aa3016a956ff8" dependencies = [ "serde", - "thiserror", + "thiserror 1.0.64", "vec-strings", ] @@ -2567,7 +2567,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" dependencies = [ "memchr", - "thiserror", + "thiserror 1.0.64", "ucd-trie", ] @@ -2591,7 +2591,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -2622,7 +2622,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -2803,7 +2803,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -2906,7 +2906,7 @@ dependencies = [ "rustc-hash", "rustls 0.23.14", "socket2", - "thiserror", + "thiserror 1.0.64", "tokio", "tracing", ] @@ -2923,7 +2923,7 @@ dependencies = [ "rustc-hash", "rustls 0.23.14", "slab", - "thiserror", + "thiserror 1.0.64", "tinyvec", "tracing", ] @@ -3026,7 +3026,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -3223,7 +3223,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.79", + "syn 2.0.87", "unicode-ident", ] @@ -3277,7 +3277,7 @@ dependencies = [ "cargo-manifest", "cargo_metadata", "serde", - "thiserror", + "thiserror 1.0.64", "toml", "tracing", ] @@ -3309,7 +3309,7 @@ dependencies = [ "serde", "strum", "strum_macros", - "thiserror", + "thiserror 2.0.0", "tokio", "toml", "typed-path", @@ -3388,7 +3388,7 @@ dependencies = [ "strum", "tar", "tempfile", - "thiserror", + "thiserror 2.0.0", "toml", "walkdir", "xattr", @@ -3510,7 +3510,7 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70d2278eb028d54ca3765d7c3f9ae100e119c07910f5731ade3564ea32a4ea20" dependencies = [ - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -3640,7 +3640,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -3703,7 +3703,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -3789,7 +3789,7 @@ checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" dependencies = [ "num-bigint", "num-traits", - "thiserror", + "thiserror 1.0.64", "time", ] @@ -3905,7 +3905,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -3929,7 +3929,7 @@ dependencies = [ "log", "pin-project", "rustls 0.21.12", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -3945,9 +3945,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.79" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", @@ -4039,7 +4039,16 @@ version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.64", +] + +[[package]] +name = "thiserror" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15291287e9bff1bc6f9ff3409ed9af665bec7a5fc8ac079ea96be07bca0e2668" +dependencies = [ + "thiserror-impl 2.0.0", ] [[package]] @@ -4050,7 +4059,18 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22efd00f33f93fa62848a7cab956c3d38c8d43095efda1decfc2b3a5dc0b8972" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", ] [[package]] @@ -4146,7 +4166,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -4233,7 +4253,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -4434,7 +4454,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", "wasm-bindgen-shared", ] @@ -4468,7 +4488,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4817,7 +4837,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 33f39203..2c9e838d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,14 +12,19 @@ resolver = "2" rust-version = "1.76.0" [workspace.dependencies] +# Internal Dependencies +rustic_backend = { path = "crates/backend", version = "0" } +rustic_core = { path = "crates/core", version = "0" } +rustic_testing = { path = "crates/testing", version = "0" } + aho-corasick = "1.1.3" anyhow = "1.0.89" bytes = "1.7.2" +displaydoc = "0.2.5" enum-map = "2.7.3" -rustic_backend = { path = "crates/backend", version = "0" } -rustic_core = { path = "crates/core", version = "0" } -rustic_testing = { path = "crates/testing", version = "0" } +log = "0.4.22" simplelog = "0.12.2" +thiserror = "2.0.0" # dev-dependencies rstest = "0.23.0" diff --git a/crates/backend/Cargo.toml b/crates/backend/Cargo.toml index af510cdf..abc8c9e7 100644 --- a/crates/backend/Cargo.toml +++ b/crates/backend/Cargo.toml @@ -50,14 +50,14 @@ rclone = ["rest", "dep:rand", "dep:semver"] rustic_core = { workspace = true } # errors -displaydoc = "0.2.5" -thiserror = "1.0.64" +displaydoc = { workspace = true } +thiserror = { workspace = true } # logging -log = "0.4.22" +log = { workspace = true } # other dependencies -bytes = "1.7.2" +bytes = { workspace = true } derive_setters = "0.1.6" humantime = "2.1.0" itertools = "0.13.0" diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 0c16f44e..2c02d814 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -41,8 +41,8 @@ rustdoc-args = ["--document-private-items", "--generate-link-to-definition"] [dependencies] # errors -displaydoc = "0.2.5" -thiserror = "1.0.64" +displaydoc = { workspace = true } +thiserror = { workspace = true } # macros derivative = "2.2.0" @@ -50,7 +50,7 @@ derive_more = { version = "1.0.0", features = ["add", "constructor", "display", derive_setters = "0.1.6" # logging -log = "0.4.22" +log = { workspace = true } # parallelize crossbeam-channel = "0.5.13" From c33014881bcdfd15c6ab92f948acbf2252dbf674 Mon Sep 17 00:00:00 2001 From: Alexander Weiss Date: Sat, 16 Nov 2024 23:09:16 +0100 Subject: [PATCH 126/129] cache: use warning and ignore non-existing cache dirs --- crates/core/src/backend/cache.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/core/src/backend/cache.rs b/crates/core/src/backend/cache.rs index e8ec526e..8d82ecb0 100644 --- a/crates/core/src/backend/cache.rs +++ b/crates/core/src/backend/cache.rs @@ -8,7 +8,7 @@ use std::{ use bytes::Bytes; use dirs::cache_dir; -use log::{error, trace, warn}; +use log::{trace, warn}; use walkdir::WalkDir; use crate::{ @@ -333,7 +333,15 @@ impl Cache { .into_iter() .inspect(|r| { if let Err(err) = r { - error!("Error while listing files: {err:?}"); + if err.depth() == 0 { + if let Some(io_err) = err.io_error() { + if io_err.kind() == io::ErrorKind::NotFound { + // ignore errors if root path doesn't exist => this should return an empty list without error + return; + } + } + } + warn!("Error while listing files: {err:?}"); } }) .filter_map(walkdir::Result::ok) From 2aa5b50f9dfe034bfe3a5ca77a83e101bf6ba67a Mon Sep 17 00:00:00 2001 From: Alexander Weiss Date: Sat, 16 Nov 2024 23:09:32 +0100 Subject: [PATCH 127/129] shorten error message --- crates/core/src/error.rs | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 7eeb557f..a61c3940 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -194,26 +194,37 @@ pub struct RusticError { impl Display for RusticError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "Well, this is embarrassing.")?; writeln!( f, - "\n`rustic_core` experienced an error related to `{}`.", + "`rustic_core` experienced an error related to `{}`.", self.kind )?; writeln!(f, "\nMessage:")?; - if self.context.is_empty() { + let context = if self.context.is_empty() { writeln!(f, "{}", self.guidance)?; + Vec::new() } else { // If there is context, we want to iterate over it // use the key to replace the placeholder in the guidance. let mut guidance = self.guidance.to_string(); - self.context.iter().for_each(|(key, value)| { - let pattern = "{".to_owned() + key + "}"; - guidance = guidance.replace(&pattern, value); - }); + let context = self + .context + .iter() + // remove context which has been used in the guidance + .filter(|(key, value)| { + let pattern = "{".to_owned() + key + "}"; + if guidance.contains(&pattern) { + guidance = guidance.replace(&pattern, value); + false + } else { + true + } + }) + .collect(); writeln!(f, "{guidance}")?; - } + context + }; if let Some(code) = &self.error_code { let default_docs_url = EcoString::from(constants::DEFAULT_DOCS_URL); @@ -258,16 +269,19 @@ impl Display for RusticError { writeln!(f, "\n\nSome additional details ...")?; - if !self.context.is_empty() { + if !context.is_empty() { writeln!(f, "\nContext:")?; - self.context + context .iter() .try_for_each(|(key, value)| writeln!(f, "- {key}: {value}"))?; } if let Some(cause) = &self.source { - writeln!(f, "\nCaused by:")?; - writeln!(f, "{cause} : (source: {:?})", cause.source())?; + write!(f, "\n Caused by:\n{cause}")?; + if let Some(cause) = cause.source() { + write!(f, " : (source: {cause:?})")?; + } + writeln!(f)?; } if let Some(severity) = &self.severity { From 388c9ca22380bc23827063af8c8b4f3014e9a3a8 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sat, 16 Nov 2024 23:46:39 +0100 Subject: [PATCH 128/129] fix some issues after review Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/error.rs | 48 ++++++++++++------- .../snapshots/errors__error_display.snap | 10 ++-- 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index a61c3940..29c2f696 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -200,7 +200,8 @@ impl Display for RusticError { self.kind )?; - writeln!(f, "\nMessage:")?; + writeln!(f)?; + writeln!(f, "Message:")?; let context = if self.context.is_empty() { writeln!(f, "{}", self.guidance)?; Vec::new() @@ -241,11 +242,13 @@ impl Display for RusticError { docs_url + "/" }; - writeln!(f, "\nFor more information, see: {docs_url}{code}")?; + writeln!(f)?; + writeln!(f, "For more information, see: {docs_url}{code}")?; } if !self.existing_issue_urls.is_empty() { - writeln!(f, "\nRelated issues:")?; + writeln!(f)?; + writeln!(f, "Related issues:")?; self.existing_issue_urls .iter() .try_for_each(|url| writeln!(f, "- {url}"))?; @@ -255,51 +258,64 @@ impl Display for RusticError { let default_issue_url = EcoString::from(constants::DEFAULT_ISSUE_URL); let new_issue_url = self.new_issue_url.as_ref().unwrap_or(&default_issue_url); + writeln!(f)?; + writeln!( f, - "\nWe believe this is a bug, please report it by opening an issue at:" + "We believe this is a bug, please report it by opening an issue at:" )?; writeln!(f, "{new_issue_url}")?; + writeln!(f)?; writeln!( f, - "\nIf you can, please attach an anonymized debug log to the issue." + "If you can, please attach an anonymized debug log to the issue." )?; - writeln!(f, "\nThank you for helping us improve rustic!")?; + writeln!(f)?; + writeln!(f, "Thank you for helping us improve rustic!")?; } - writeln!(f, "\n\nSome additional details ...")?; + writeln!(f)?; + writeln!(f)?; + + writeln!(f, "Some additional details ...")?; if !context.is_empty() { - writeln!(f, "\nContext:")?; + writeln!(f)?; + writeln!(f, "Context:")?; context .iter() .try_for_each(|(key, value)| writeln!(f, "- {key}: {value}"))?; } if let Some(cause) = &self.source { - write!(f, "\n Caused by:\n{cause}")?; - if let Some(cause) = cause.source() { - write!(f, " : (source: {cause:?})")?; + writeln!(f)?; + writeln!(f, "Caused by:")?; + writeln!(f, "{cause}")?; + if let Some(source) = cause.source() { + write!(f, " : (source: {source:?})")?; } writeln!(f)?; } if let Some(severity) = &self.severity { - writeln!(f, "\nSeverity: {severity}")?; + writeln!(f)?; + writeln!(f, "Severity: {severity}")?; } if let Some(status) = &self.status { - writeln!(f, "\nStatus: {status}")?; + writeln!(f)?; + writeln!(f, "Status: {status}")?; } if let Some(backtrace) = &self.backtrace { - writeln!(f, "\nBacktrace:")?; - writeln!(f, "{backtrace}")?; + writeln!(f)?; + writeln!(f, "Backtrace:")?; + write!(f, "{backtrace}")?; if backtrace.status() == BacktraceStatus::Disabled { writeln!( f, - "\nTo enable backtraces, set the RUST_BACKTRACE=\"1\" environment variable." + " (set 'RUST_BACKTRACE=\"1\"' environment variable to enable)" )?; } } diff --git a/crates/core/tests/snapshots/errors__error_display.snap b/crates/core/tests/snapshots/errors__error_display.snap index b9be299d..3acc4c8b 100644 --- a/crates/core/tests/snapshots/errors__error_display.snap +++ b/crates/core/tests/snapshots/errors__error_display.snap @@ -2,8 +2,6 @@ source: crates/core/tests/errors.rs expression: error --- -Well, this is embarrassing. - `rustic_core` experienced an error related to `input/output operations`. Message: @@ -27,17 +25,15 @@ Thank you for helping us improve rustic! Some additional details ... Context: -- path: /path/to/file - called: used s3 backend Caused by: -Networking Error : (source: None) +Networking Error + Severity: Error Status: Permanent Backtrace: -disabled backtrace - -To enable backtraces, set the RUST_BACKTRACE="1" environment variable. +disabled backtrace (set 'RUST_BACKTRACE="1"' environment variable to enable) From 74a0bd37cd6faf2bcb96ed5173c22925878b3642 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sat, 16 Nov 2024 23:59:18 +0100 Subject: [PATCH 129/129] clippy: allow many lines for impl Display Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/error.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 29c2f696..5d98035f 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -193,6 +193,7 @@ pub struct RusticError { } impl Display for RusticError { + #[allow(clippy::too_many_lines)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!( f,