diff --git a/Cargo.lock b/Cargo.lock index d1dd0903..d868b5ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -127,7 +127,6 @@ dependencies = [ "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", - "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "globset 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "isatty 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -187,14 +186,6 @@ name = "either" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "error-chain" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "error-chain" version = "0.12.0" @@ -755,7 +746,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "927121f5407de9956180ff5e936fe3cf4324279280001cd56b669d28ee7e9150" "checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9" "checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0" -"checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3" "checksum error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07e791d3be96241c77c43846b665ef1384606da2cd2a48730abe606a12906e02" "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" diff --git a/Cargo.toml b/Cargo.toml index 2cc19387..fa375cdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ doc = false # Conflicts with library docs which are more useful. [dependencies] chrono = "0.2" clap = "2.32" -error-chain = "0.11" isatty = "0.1" rayon = "1.0.2" regex = "0.2" diff --git a/TODO.md b/TODO.md index cdd8ffd1..fe305e0e 100644 --- a/TODO.md +++ b/TODO.md @@ -85,9 +85,6 @@ Clean message, and test for it, when the archive directory just doesn't exist. * Add a 'high-level' module similar to the CLI, but not coupled to it? -* Maybe don't use `error_chain`? I have an unjustified feeling it slows down - compilation. Perhaps use `Failure`. - * Report warnings by failures/errors that are passed to the UI rather than returned. @@ -213,7 +210,6 @@ and if pre-measuring is turned off we could stay in this mode. * Make a macro like `try!` that logs when it sees an error? * Errors to stderr rather than stdout? Hard to reconcile with use of terminal for colored errors. -* Maybe have Conserve-specific error types rather than `io::Error` everywhere? ## Store/restore metadata diff --git a/copyright b/copyright index d59e76a8..32160d88 100644 --- a/copyright +++ b/copyright @@ -4,5 +4,5 @@ Upstream-Contact: Martin Pool Source: https://github.com/sourcefrog/conserve Files: * -Copyright: 2012-2016 Martin Pool +Copyright: 2012-2018 Martin Pool Licence: GPL-2+ diff --git a/src/archive.rs b/src/archive.rs index b95161b9..d57981f8 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -49,12 +49,11 @@ impl Archive { let header_filename = path.join(HEADER_FILENAME); let report = Report::new(); jsonio::write(&header_filename, &header, &report) - .chain_err(|| format!("Failed to write archive header: {:?}", header_filename))?; - Ok(Archive { - path: path.to_path_buf(), - report, - block_dir, - }) + .and(Ok(Archive { + path: path.to_path_buf(), + report, + block_dir, + })) } /// Open an existing archive. @@ -64,15 +63,14 @@ impl Archive { let path = path.as_ref(); let header_path = path.join(HEADER_FILENAME); if !file_exists(&header_path)? { - return Err(ErrorKind::NotAnArchive(path.into()).into()); + return Err(Error::NotAnArchive(path.into())); } let block_dir = BlockDir::new(&path.join(BLOCK_DIR)); - let header: ArchiveHeader = - jsonio::read(&header_path, &report).chain_err(|| "Failed to read archive header")?; + let header: ArchiveHeader = jsonio::read(&header_path, &report)?; if header.conserve_archive_version != ARCHIVE_VERSION { - return Err( - ErrorKind::UnsupportedArchiveVersion(header.conserve_archive_version).into(), - ); + return Err(Error::UnsupportedArchiveVersion( + header.conserve_archive_version, + )); } Ok(Archive { path: path.to_path_buf(), @@ -119,7 +117,7 @@ impl Archive { }; } } - l.ok_or_else(|| ErrorKind::ArchiveEmpty.into()) + l.ok_or(Error::ArchiveEmpty) } /// Return the last completely-written band id. @@ -130,7 +128,7 @@ impl Archive { return Ok(b); } } - Err(ErrorKind::NoCompleteBands.into()) + Err(Error::NoCompleteBands) } /// Return a sorted set containing all the blocks referenced by all bands. @@ -171,7 +169,7 @@ mod tests { use std::io::Read; use super::*; - use errors::ErrorKind; + use errors::Error; use test_fixtures::ScratchArchive; #[test] @@ -217,13 +215,13 @@ mod tests { header_file.read_to_string(&mut contents).unwrap(); assert_eq!(contents, "{\"conserve_archive_version\":\"0.5\"}\n"); - match *af.last_band_id().unwrap_err().kind() { - ErrorKind::ArchiveEmpty => (), + match af.last_band_id().unwrap_err() { + Error::ArchiveEmpty => (), ref x => panic!("Unexpected error {:?}", x), } - match *af.last_complete_band().unwrap_err().kind() { - ErrorKind::NoCompleteBands => (), + match af.last_complete_band().unwrap_err() { + Error::NoCompleteBands => (), ref x => panic!("Unexpected error {:?}", x), } diff --git a/src/band.rs b/src/band.rs index 0c85b0ec..5af7f3e4 100644 --- a/src/band.rs +++ b/src/band.rs @@ -61,7 +61,7 @@ impl Band { /// The Band gets the next id after those that already exist. pub fn create(archive: &Archive) -> Result { let new_band_id = match archive.last_band_id() { - Err(Error(ErrorKind::ArchiveEmpty, _)) => BandId::zero(), + Err(Error::ArchiveEmpty) => BandId::zero(), Ok(b) => b.next_sibling(), Err(e) => return Err(e), }; @@ -240,7 +240,7 @@ mod tests { let band_id = BandId::from_string("b0001").unwrap(); Band::create_specific_id(&af, band_id.clone()).unwrap(); let e = Band::create_specific_id(&af, band_id).unwrap_err(); - if let ErrorKind::Io(ref ioerror) = *e.kind() { + if let Error::IoError(ref ioerror) = e { assert_eq!(ioerror.kind(), io::ErrorKind::AlreadyExists); } else { panic!("expected an ioerror, got {:?}", e); diff --git a/src/bandid.rs b/src/bandid.rs index 398671e3..0324f72b 100644 --- a/src/bandid.rs +++ b/src/bandid.rs @@ -39,7 +39,7 @@ impl BandId { /// Make a new BandId from a string form. pub fn from_string(s: &str) -> Result { - let nope = Err(ErrorKind::InvalidVersion.into()); + let nope = Err(Error::InvalidVersion); if !s.starts_with('b') { return nope; } @@ -53,7 +53,6 @@ impl BandId { if seqs.is_empty() { nope } else { - // This rebuilds a new string form to get it into the canonical form. Ok(BandId::new(&seqs)) } } diff --git a/src/bin/conserve.rs b/src/bin/conserve.rs index 54684351..ab42711e 100644 --- a/src/bin/conserve.rs +++ b/src/bin/conserve.rs @@ -249,14 +249,8 @@ fn make_clap<'a, 'b>() -> clap::App<'a, 'b> { } fn show_chained_errors(report: &Report, e: &Error) { + // TODO: Implement this again when core error types have backtraces. report.problem(&format!("{}", e)); - for suberr in e.iter().skip(1) { - // First was already printed - report.problem(&format!(" {}", suberr)); - } - if let Some(bt) = e.backtrace() { - report.problem(&format!("{:?}", bt)); - } } fn init(subm: &ArgMatches, report: &Report) -> Result<()> { diff --git a/src/blockdir.rs b/src/blockdir.rs index a63a90a4..a9f01f27 100644 --- a/src/blockdir.rs +++ b/src/blockdir.rs @@ -311,7 +311,7 @@ impl Block { Err(e) => { report.increment("block.corrupt", 1); report.problem(&format!("Block file {:?} read error {:?}", self.path, e)); - return Err(ErrorKind::BlockCorrupt(self.hash.to_string()).into()); + return Err(Error::BlockCorrupt(self.path.clone())); } }; report.increment("block.read", 1); @@ -337,7 +337,7 @@ impl Block { "Block file {:?} has actual decompressed hash {:?}", self.path, actual_hash )); - return Err(ErrorKind::BlockCorrupt(self.hash.to_string()).into()); + return Err(Error::BlockCorrupt(self.path.clone())); } Ok(()) } diff --git a/src/errors.rs b/src/errors.rs index b009edd4..c5c1c25f 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -3,41 +3,82 @@ //! Conserve error types. -use rustc_serialize; +use std::error; +use std::fmt; use std::io; use std::path::PathBuf; -use BandId; +use rustc_serialize; -error_chain! { - foreign_links { - Io(io::Error); - JsonDecode(rustc_serialize::json::DecoderError); - } +use super::*; - errors { - BlockCorrupt(block_hash: String) { - } - NotAnArchive(path: PathBuf) { - display("Not a Conserve archive: {:?}", path) - } - UnsupportedArchiveVersion(version: String) { - display("Unsupported archive version: {:?}", version) - } - DestinationNotEmpty(destination: PathBuf) { - display("Destination directory not empty: {:?}", destination) - } - ArchiveEmpty { - display("Archive is empty") - } - NoCompleteBands { - display("Archive has no complete bands") - } - InvalidVersion { - display("Invalid version number") +/// Conserve specific error. +#[derive(Debug)] +pub enum Error { + BlockCorrupt(PathBuf), + NotAnArchive(PathBuf), + NotADirectory(PathBuf), + NotAFile(PathBuf), + UnsupportedArchiveVersion(String), + DestinationNotEmpty(PathBuf), + ArchiveEmpty, + NoCompleteBands, + InvalidVersion, + BandIncomplete(BandId), + IoError(io::Error), + // TODO: Include the path in the json error. + JsonDecode(rustc_serialize::json::DecoderError), + BadGlob(globset::Error), + IndexCorrupt(PathBuf), +} + +pub type Result = std::result::Result; + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::DestinationNotEmpty(d) => write!(f, "Destination directory not empty: {:?}", d), + Error::ArchiveEmpty => write!(f, "Archive is empty"), + Error::NoCompleteBands => write!(f, "Archive has no complete bands"), + Error::InvalidVersion => write!(f, "Invalid version number"), + Error::NotAnArchive(p) => write!(f, "Not a Conserve archive: {:?}", p), + Error::BandIncomplete(b) => write!(f, "Band {} is incomplete", b), + Error::UnsupportedArchiveVersion(v) => write!( + f, + "Archive version {:?} is not supported by Conserve {}", + v, + version() + ), + _ => write!(f, "{:?}", self), } - BandIncomplete(band_id: BandId) { - display("Band {} is incomplete", band_id) + } +} + +impl std::error::Error for Error { + fn cause(&self) -> Option<&dyn error::Error> { + match self { + Error::IoError(c) => Some(c), + Error::JsonDecode(c) => Some(c), + Error::BadGlob(c) => Some(c), + _ => None, } } } + +impl From for Error { + fn from(c: io::Error) -> Error { + Error::IoError(c) + } +} + +impl From for Error { + fn from(c: globset::Error) -> Error { + Error::BadGlob(c) + } +} + +impl From for Error { + fn from(c: rustc_serialize::json::DecoderError) -> Error { + Error::JsonDecode(c) + } +} diff --git a/src/excludes.rs b/src/excludes.rs index 4b966450..9c1d8112 100644 --- a/src/excludes.rs +++ b/src/excludes.rs @@ -8,16 +8,15 @@ use super::*; pub fn from_strings, S: AsRef>(excludes: I) -> Result { let mut builder = GlobSetBuilder::new(); - for e in excludes { - let exclude = e.as_ref(); - builder.add( - Glob::new(exclude) - .chain_err(|| format!("Failed to parse exclude value: {}", exclude))?, - ); + for i in excludes { + match Glob::new(i.as_ref()) { + Ok(g) => builder.add(g), + Err(e) => { + return Err(e.into()); + } + }; } - builder - .build() - .chain_err(|| "Failed to build exclude patterns") + builder.build().or_else(|e| Err(e.into())) } pub fn excludes_nothing() -> GlobSet { diff --git a/src/index.rs b/src/index.rs index 12bbdf11..092a53ed 100644 --- a/src/index.rs +++ b/src/index.rs @@ -283,10 +283,10 @@ impl Iter { self.report.increment("index.hunk", 1); let start_parse = Instant::now(); - let index_json = str::from_utf8(&index_bytes) - .chain_err(|| format!("index file {:?} is not UTF-8", hunk_path))?; - let entries: Vec = json::decode(index_json) - .chain_err(|| format!("couldn't deserialize index hunk {:?}", hunk_path))?; + let index_json = + str::from_utf8(&index_bytes).or_else(|_| Err(Error::IndexCorrupt(hunk_path.clone())))?; + let entries: Vec = + json::decode(index_json).or_else(|e| Err(Error::JsonDecode(e)))?; if entries.is_empty() { self.report .problem(&format!("Index hunk {} is empty", hunk_path.display())); diff --git a/src/io.rs b/src/io.rs index 78abd765..172ae33a 100644 --- a/src/io.rs +++ b/src/io.rs @@ -83,7 +83,7 @@ pub fn directory_exists(path: &Path) -> Result { if metadata.is_dir() { Ok(true) } else { - Err("exists but not a directory".into()) + Err(Error::NotADirectory(path.into())) } } Err(e) => match e.kind() { @@ -100,7 +100,7 @@ pub fn file_exists(path: &Path) -> Result { if metadata.is_file() { Ok(true) } else { - Err("exists but not a file".into()) + Err(Error::NotAFile(path.into())) } } Err(e) => match e.kind() { @@ -116,12 +116,12 @@ pub fn require_empty_directory(path: &Path) -> Result<()> { if e.kind() == io::ErrorKind::AlreadyExists { // Exists and hopefully empty? if std::fs::read_dir(&path)?.next().is_some() { - Err(e).chain_err(|| format!("Directory exists and is not empty {:?}", path)) + Err(Error::DestinationNotEmpty(path.into())) } else { Ok(()) // Exists and empty } } else { - Err(e).chain_err(|| format!("Failed to create directory {:?}", path)) + Err(Error::IoError(e)) } } else { Ok(()) // Created diff --git a/src/jsonio.rs b/src/jsonio.rs index 37adb52e..245fa2aa 100644 --- a/src/jsonio.rs +++ b/src/jsonio.rs @@ -1,5 +1,5 @@ // Conserve backup system. -// Copyright 2015, 2016 Martin Pool. +// Copyright 2015, 2016, 2018 Martin Pool. //! Read and write JSON files. @@ -23,13 +23,12 @@ pub fn write(path: &Path, obj: &T, report: &Report) -> Result<()> } pub fn read(path: &Path, _report: &Report) -> Result { - // TODO: Send something to the Report. - // At present this is used only for small metadata files so measurement is not - // critical. - let mut f = File::open(path).chain_err(|| format!("Failed to open {:?}", path))?; + // TODO: Send something to the Report. At present this is used only for + // small metadata files so measurement is not critical. + let mut f = File::open(path).or_else(|e| Err(Error::IoError(e)))?; let mut buf = String::new(); let _bytes_read = f.read_to_string(&mut buf)?; - json::decode(&buf).chain_err(|| format!("Couldn't deserialize {:?}", path)) + json::decode(&buf).or_else(|e| Err(e.into())) } #[cfg(test)] diff --git a/src/lib.rs b/src/lib.rs index 082fb8f7..aaaa5c83 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,11 +4,6 @@ //! //! For user documentation and an overview see http://conserve.fyi/. -#![recursion_limit = "1024"] // Needed by error-chain - -#[macro_use] -extern crate error_chain; - extern crate blake2_rfc; extern crate chrono; extern crate isatty; diff --git a/src/restore.rs b/src/restore.rs index 5fef8e85..9e2d0808 100644 --- a/src/restore.rs +++ b/src/restore.rs @@ -100,11 +100,12 @@ impl tree::WriteTree for RestoreTree { } /// The destination must either not exist, or be an empty directory. +// TODO: Merge with or just use require_empty_directory? fn require_empty_destination(dest: &Path) -> Result<()> { match fs::read_dir(&dest) { Ok(mut it) => { if it.next().is_some() { - Err(ErrorKind::DestinationNotEmpty(dest.to_path_buf()).into()) + Err(Error::DestinationNotEmpty(dest.to_path_buf())) } else { Ok(()) } diff --git a/src/stored_tree.rs b/src/stored_tree.rs index 1fb1e4bb..06469481 100644 --- a/src/stored_tree.rs +++ b/src/stored_tree.rs @@ -46,7 +46,7 @@ impl StoredTree { pub fn open_version(archive: &Archive, band_id: &BandId) -> Result { let band = Band::open(archive, band_id)?; if !band.is_closed()? { - return Err(ErrorKind::BandIncomplete(band_id.clone()).into()); + return Err(Error::BandIncomplete(band_id.clone())); } Ok(StoredTree::new(archive, band, excludes::excludes_nothing())) }