Convert BlobError into an enum

This deletes a lot of code and complexity.  Though comes at some cost:

- The type no longer fits in a register and will always be on the
  stack.

- Constructing the errors is more verbose, no more auto Into casting.
This commit is contained in:
Floris Bruynooghe
2019-11-30 02:57:26 +01:00
committed by Floris Bruynooghe
parent 084a87ed61
commit 74a4691f29
3 changed files with 86 additions and 186 deletions

View File

@@ -32,11 +32,11 @@ impl<'a> BlobObject<'a> {
/// ///
/// # Errors /// # Errors
/// ///
/// [BlobErrorKind::CreateFailure] is used when the file could not /// [BlobError::CreateFailure] is used when the file could not
/// be created. You can expect [BlobError.cause] to contain an /// be created. You can expect [BlobError.cause] to contain an
/// underlying error. /// underlying error.
/// ///
/// [BlobErrorKind::WriteFailure] is used when the file could not /// [BlobError::WriteFailure] is used when the file could not
/// be written to. You can expect [BlobError.cause] to contain an /// be written to. You can expect [BlobError.cause] to contain an
/// underlying error. /// underlying error.
pub fn create( pub fn create(
@@ -48,7 +48,12 @@ impl<'a> BlobObject<'a> {
let (stem, ext) = BlobObject::sanitise_name(suggested_name.as_ref()); let (stem, ext) = BlobObject::sanitise_name(suggested_name.as_ref());
let (name, mut file) = BlobObject::create_new_file(&blobdir, &stem, &ext)?; let (name, mut file) = BlobObject::create_new_file(&blobdir, &stem, &ext)?;
file.write_all(data) file.write_all(data)
.map_err(|err| BlobError::new_write_failure(blobdir, &name, err))?; .map_err(|err| BlobError::WriteFailure {
blobdir: blobdir.to_path_buf(),
blobname: name.clone(),
cause: err,
backtrace: failure::Backtrace::new(),
})?;
let blob = BlobObject { let blob = BlobObject {
blobdir, blobdir,
name: format!("$BLOBDIR/{}", name), name: format!("$BLOBDIR/{}", name),
@@ -71,18 +76,25 @@ impl<'a> BlobObject<'a> {
Ok(file) => return Ok((name, file)), Ok(file) => return Ok((name, file)),
Err(err) => { Err(err) => {
if attempt == max_attempt { if attempt == max_attempt {
return Err(BlobError::new_create_failure(dir, &name, err)); return Err(BlobError::CreateFailure {
blobdir: dir.to_path_buf(),
blobname: name,
cause: err,
backtrace: failure::Backtrace::new(),
});
} else { } else {
name = format!("{}-{}{}", stem, rand::random::<u32>(), ext); name = format!("{}-{}{}", stem, rand::random::<u32>(), ext);
} }
} }
} }
} }
Err(BlobError::new_create_failure( // This is supposed to be unreachable, but the compiler doesn't know.
dir, Err(BlobError::CreateFailure {
&name, blobdir: dir.to_path_buf(),
format_err!("Unreachable code - supposedly"), blobname: name,
)) cause: std::io::Error::new(std::io::ErrorKind::Other, "supposedly unreachable"),
backtrace: failure::Backtrace::new(),
})
} }
/// Creates a new blob object with unique name by copying an existing file. /// Creates a new blob object with unique name by copying an existing file.
@@ -95,24 +107,35 @@ impl<'a> BlobObject<'a> {
/// # Errors /// # Errors
/// ///
/// In addition to the errors in [BlobObject::create] the /// In addition to the errors in [BlobObject::create] the
/// [BlobErrorKind::CopyFailure] is used when the data can not be /// [BlobError::CopyFailure] is used when the data can not be
/// copied. /// copied.
pub fn create_and_copy( pub fn create_and_copy(
context: &'a Context, context: &'a Context,
src: impl AsRef<Path>, src: impl AsRef<Path>,
) -> std::result::Result<BlobObject<'a>, BlobError> { ) -> std::result::Result<BlobObject<'a>, BlobError> {
let mut src_file = fs::File::open(src.as_ref()).map_err(|err| { let mut src_file = fs::File::open(src.as_ref()).map_err(|err| BlobError::CopyFailure {
BlobError::new_copy_failure(context.get_blobdir(), "", src.as_ref(), err) blobdir: context.get_blobdir().to_path_buf(),
blobname: String::from(""),
src: src.as_ref().to_path_buf(),
cause: err,
backtrace: failure::Backtrace::new(),
})?; })?;
let (stem, ext) = BlobObject::sanitise_name(&src.as_ref().to_string_lossy()); let (stem, ext) = BlobObject::sanitise_name(&src.as_ref().to_string_lossy());
let (name, mut dst_file) = BlobObject::create_new_file(context.get_blobdir(), &stem, &ext)?; let (name, mut dst_file) = BlobObject::create_new_file(context.get_blobdir(), &stem, &ext)?;
let name_for_err = name.clone();
std::io::copy(&mut src_file, &mut dst_file).map_err(|err| { std::io::copy(&mut src_file, &mut dst_file).map_err(|err| {
{ {
// Attempt to remove the failed file, swallow errors resulting from that. // Attempt to remove the failed file, swallow errors resulting from that.
let path = context.get_blobdir().join(&name); let path = context.get_blobdir().join(&name_for_err);
fs::remove_file(path).ok(); fs::remove_file(path).ok();
} }
BlobError::new_copy_failure(context.get_blobdir(), &name, src.as_ref(), err) BlobError::CopyFailure {
blobdir: context.get_blobdir().to_path_buf(),
blobname: name_for_err,
src: src.as_ref().to_path_buf(),
cause: err,
backtrace: failure::Backtrace::new(),
}
})?; })?;
let blob = BlobObject { let blob = BlobObject {
blobdir: context.get_blobdir(), blobdir: context.get_blobdir(),
@@ -156,10 +179,10 @@ impl<'a> BlobObject<'a> {
/// ///
/// # Errors /// # Errors
/// ///
/// [BlobErrorKind::WrongBlobdir] is used if the path is not in /// [BlobError::WrongBlobdir] is used if the path is not in
/// the blob directory. /// the blob directory.
/// ///
/// [BlobErrorKind::WrongName] is used if the file name does not /// [BlobError::WrongName] is used if the file name does not
/// remain identical after sanitisation. /// remain identical after sanitisation.
pub fn from_path( pub fn from_path(
context: &Context, context: &Context,
@@ -168,13 +191,21 @@ impl<'a> BlobObject<'a> {
let rel_path = path let rel_path = path
.as_ref() .as_ref()
.strip_prefix(context.get_blobdir()) .strip_prefix(context.get_blobdir())
.map_err(|_| BlobError::new_wrong_blobdir(context.get_blobdir(), path.as_ref()))?; .map_err(|_| BlobError::WrongBlobdir {
blobdir: context.get_blobdir().to_path_buf(),
src: path.as_ref().to_path_buf(),
backtrace: failure::Backtrace::new(),
})?;
if !BlobObject::is_acceptible_blob_name(&rel_path) { if !BlobObject::is_acceptible_blob_name(&rel_path) {
return Err(BlobError::new_wrong_name(path.as_ref())); return Err(BlobError::WrongName {
blobname: path.as_ref().to_path_buf(),
backtrace: failure::Backtrace::new(),
});
} }
let name = rel_path let name = rel_path.to_str().ok_or_else(|| BlobError::WrongName {
.to_str() blobname: path.as_ref().to_path_buf(),
.ok_or_else(|| BlobError::new_wrong_name(path.as_ref()))?; backtrace: failure::Backtrace::new(),
})?;
BlobObject::from_name(context, name.to_string()) BlobObject::from_name(context, name.to_string())
} }
@@ -187,7 +218,7 @@ impl<'a> BlobObject<'a> {
/// ///
/// # Errors /// # Errors
/// ///
/// [BlobErrorKind::WrongName] is used if the name is not a valid /// [BlobError::WrongName] is used if the name is not a valid
/// blobname, i.e. if [BlobObject::sanitise_name] does modify the /// blobname, i.e. if [BlobObject::sanitise_name] does modify the
/// provided name. /// provided name.
pub fn from_name( pub fn from_name(
@@ -199,7 +230,10 @@ impl<'a> BlobObject<'a> {
false => name, false => name,
}; };
if !BlobObject::is_acceptible_blob_name(&name) { if !BlobObject::is_acceptible_blob_name(&name) {
return Err(BlobError::new_wrong_name(name)); return Err(BlobError::WrongName {
blobname: PathBuf::from(name),
backtrace: failure::Backtrace::new(),
});
} }
Ok(BlobObject { Ok(BlobObject {
blobdir: context.get_blobdir(), blobdir: context.get_blobdir(),
@@ -324,171 +358,50 @@ impl<'a> fmt::Display for BlobObject<'a> {
} }
/// Errors for the [BlobObject]. /// Errors for the [BlobObject].
/// #[derive(Fail, Debug)]
/// To keep the return type small and thus the happy path fast this pub enum BlobError {
/// stores everything on the heap.
#[derive(Debug)]
pub struct BlobError {
inner: Box<BlobErrorInner>,
}
#[derive(Debug)]
struct BlobErrorInner {
kind: BlobErrorKind,
data: BlobErrorData,
backtrace: failure::Backtrace,
}
/// Error kind for [BlobError].
///
/// Each error kind has associated data in the [BlobErrorData].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlobErrorKind {
/// Failed to create the blob.
CreateFailure,
/// Failed to write data to blob.
WriteFailure,
/// Failed to copy data to blob.
CopyFailure,
/// Blob is not in the blobdir.
WrongBlobdir,
/// Blob has a bad name.
///
/// E.g. the name is not sanitised correctly or contains a
/// sub-directory.
WrongName,
}
/// Associated data for each [BlobError] error kind.
///
/// This is not stored directly on the [BlobErrorKind] so that the
/// kind can stay trivially Copy and Eq. It is however possible to
/// create a [BlobError] with mismatching [BlobErrorKind] and
/// [BlobErrorData], don't do that.
///
/// Any blobname stored here is the bare name, without the `$BLOBDIR`
/// prefix. All data is owned so that errors do not need to be tied
/// to any lifetimes.
#[derive(Debug)]
enum BlobErrorData {
CreateFailure { CreateFailure {
blobdir: PathBuf, blobdir: PathBuf,
blobname: String, blobname: String,
cause: failure::Error, #[cause]
cause: std::io::Error,
backtrace: failure::Backtrace,
}, },
WriteFailure { WriteFailure {
blobdir: PathBuf, blobdir: PathBuf,
blobname: String, blobname: String,
cause: failure::Error, #[cause]
cause: std::io::Error,
backtrace: failure::Backtrace,
}, },
CopyFailure { CopyFailure {
blobdir: PathBuf, blobdir: PathBuf,
blobname: String, blobname: String,
src: PathBuf, src: PathBuf,
cause: failure::Error, #[cause]
cause: std::io::Error,
backtrace: failure::Backtrace,
}, },
WrongBlobdir { WrongBlobdir {
blobdir: PathBuf, blobdir: PathBuf,
src: PathBuf, src: PathBuf,
backtrace: failure::Backtrace,
}, },
WrongName { WrongName {
blobname: PathBuf, blobname: PathBuf,
backtrace: failure::Backtrace,
}, },
} }
impl BlobError { // Implementing Display is done by hand because the failure
pub fn kind(&self) -> BlobErrorKind { // #[fail(display = "...")] syntax does not allow using
self.inner.kind // `blobdir.display()`.
}
fn new_create_failure(
blobdir: impl Into<PathBuf>,
blobname: impl Into<String>,
cause: impl Into<failure::Error>,
) -> BlobError {
BlobError {
inner: Box::new(BlobErrorInner {
kind: BlobErrorKind::CreateFailure,
data: BlobErrorData::CreateFailure {
blobdir: blobdir.into(),
blobname: blobname.into(),
cause: cause.into(),
},
backtrace: failure::Backtrace::new(),
}),
}
}
fn new_write_failure(
blobdir: impl Into<PathBuf>,
blobname: impl Into<String>,
cause: impl Into<failure::Error>,
) -> BlobError {
BlobError {
inner: Box::new(BlobErrorInner {
kind: BlobErrorKind::WriteFailure,
data: BlobErrorData::WriteFailure {
blobdir: blobdir.into(),
blobname: blobname.into(),
cause: cause.into(),
},
backtrace: failure::Backtrace::new(),
}),
}
}
fn new_copy_failure(
blobdir: impl Into<PathBuf>,
blobname: impl Into<String>,
src: impl Into<PathBuf>,
cause: impl Into<failure::Error>,
) -> BlobError {
BlobError {
inner: Box::new(BlobErrorInner {
kind: BlobErrorKind::CopyFailure,
data: BlobErrorData::CopyFailure {
blobdir: blobdir.into(),
blobname: blobname.into(),
src: src.into(),
cause: cause.into(),
},
backtrace: failure::Backtrace::new(),
}),
}
}
fn new_wrong_blobdir(blobdir: impl Into<PathBuf>, src: impl Into<PathBuf>) -> BlobError {
BlobError {
inner: Box::new(BlobErrorInner {
kind: BlobErrorKind::WrongBlobdir,
data: BlobErrorData::WrongBlobdir {
blobdir: blobdir.into(),
src: src.into(),
},
backtrace: failure::Backtrace::new(),
}),
}
}
fn new_wrong_name(blobname: impl Into<PathBuf>) -> BlobError {
BlobError {
inner: Box::new(BlobErrorInner {
kind: BlobErrorKind::WrongName,
data: BlobErrorData::WrongName {
blobname: blobname.into(),
},
backtrace: failure::Backtrace::new(),
}),
}
}
}
impl fmt::Display for BlobError { impl fmt::Display for BlobError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Match on the data rather than kind, they are equivalent for // Match on the data rather than kind, they are equivalent for
// identifying purposes but contain the actual data we need. // identifying purposes but contain the actual data we need.
match &self.inner.data { match &self {
BlobErrorData::CreateFailure { BlobError::CreateFailure {
blobdir, blobname, .. blobdir, blobname, ..
} => write!( } => write!(
f, f,
@@ -496,7 +409,7 @@ impl fmt::Display for BlobError {
blobname, blobname,
blobdir.display() blobdir.display()
), ),
BlobErrorData::WriteFailure { BlobError::WriteFailure {
blobdir, blobname, .. blobdir, blobname, ..
} => write!( } => write!(
f, f,
@@ -504,7 +417,7 @@ impl fmt::Display for BlobError {
blobname, blobname,
blobdir.display() blobdir.display()
), ),
BlobErrorData::CopyFailure { BlobError::CopyFailure {
blobdir, blobdir,
blobname, blobname,
src, src,
@@ -516,34 +429,19 @@ impl fmt::Display for BlobError {
blobname, blobname,
blobdir.display(), blobdir.display(),
), ),
BlobErrorData::WrongBlobdir { blobdir, src } => write!( BlobError::WrongBlobdir { blobdir, src, .. } => write!(
f, f,
"File path {} is not in blobdir {}", "File path {} is not in blobdir {}",
src.display(), src.display(),
blobdir.display(), blobdir.display(),
), ),
BlobErrorData::WrongName { blobname } => { BlobError::WrongName { blobname, .. } => {
write!(f, "Blob has a bad name: {}", blobname.display(),) write!(f, "Blob has a bad name: {}", blobname.display(),)
} }
} }
} }
} }
impl failure::Fail for BlobError {
fn cause(&self) -> Option<&dyn failure::Fail> {
match &self.inner.data {
BlobErrorData::CreateFailure { cause, .. }
| BlobErrorData::WriteFailure { cause, .. }
| BlobErrorData::CopyFailure { cause, .. } => Some(cause.as_fail()),
_ => None,
}
}
fn backtrace(&self) -> Option<&failure::Backtrace> {
Some(&self.inner.backtrace)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
use itertools::Itertools; use itertools::Itertools;
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use crate::blob::{BlobErrorKind, BlobObject}; use crate::blob::{BlobError, BlobObject};
use crate::chatlist::*; use crate::chatlist::*;
use crate::config::*; use crate::config::*;
use crate::constants::*; use crate::constants::*;
@@ -1797,8 +1797,8 @@ pub fn set_chat_profile_image(
)); ));
} else { } else {
let image_blob = BlobObject::from_path(context, Path::new(new_image.as_ref())).or_else( let image_blob = BlobObject::from_path(context, Path::new(new_image.as_ref())).or_else(
|err| match err.kind() { |err| match err {
BlobErrorKind::WrongBlobdir => { BlobError::WrongBlobdir { .. } => {
BlobObject::create_and_copy(context, Path::new(new_image.as_ref())) BlobObject::create_and_copy(context, Path::new(new_image.as_ref()))
} }
_ => Err(err), _ => Err(err),

View File

@@ -321,7 +321,6 @@ mod tests {
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
use crate::blob::BlobErrorKind;
use crate::test_utils::*; use crate::test_utils::*;
#[test] #[test]
@@ -404,7 +403,10 @@ mod tests {
// Blob does not exist yet, expect BlobError. // Blob does not exist yet, expect BlobError.
let err = p.get_blob(Param::File, &t.ctx, false).unwrap_err(); let err = p.get_blob(Param::File, &t.ctx, false).unwrap_err();
assert_eq!(err.kind(), BlobErrorKind::WrongBlobdir); match err {
BlobError::WrongBlobdir { .. } => (),
_ => panic!("wrong error type/variant: {:?}", err),
}
fs::write(fname, b"boo").unwrap(); fs::write(fname, b"boo").unwrap();
let blob = p.get_blob(Param::File, &t.ctx, true).unwrap().unwrap(); let blob = p.get_blob(Param::File, &t.ctx, true).unwrap().unwrap();