Use invariant instead of unwrap and expect

Add the `Invariant` trait, which provides `Invariant::invariant` and
`Invariant::invariant_unwrap` methods, and use them instead of unwrap
and expect.

I think these methods are a bit clearer than `unwrap` and `expect`,
since they more clearly document intent, i.e. that the thing passed to
`invariant` should be a description of an invariant that should always
be true, and should provide better error messages.

Replace uses of `unwrap` and `expect` with `invariant`.

type: reform
fixes:
- https://github.com/casey/intermodal/issues/167
This commit is contained in:
Casey Rodarmor 2020-04-18 14:36:54 -07:00
parent faf46c0f0e
commit a6bf752791
No known key found for this signature in database
GPG Key ID: 556186B153EC6FE0
13 changed files with 60 additions and 17 deletions

View File

@ -4,7 +4,8 @@ Changelog
UNRELEASED - 2020-04-18
-----------------------
- :white_check_mark: [`xxxxxxxxxxxx`](https://github.com/casey/intermodal/commits/master) Test that globs match torrent contents - Fixes [#377](https://github.com/casey/intermodal/issues/377) - _Casey Rodarmor <casey@rodarmor.com>_
- :art: [`xxxxxxxxxxxx`](https://github.com/casey/intermodal/commits/master) Use `invariant` instead of `unwrap` and `expect` - Fixes [#167](https://github.com/casey/intermodal/issues/167) - _Casey Rodarmor <casey@rodarmor.com>_
- :white_check_mark: [`faf46c0f0e6f`](https://github.com/casey/intermodal/commit/faf46c0f0e6fd4e4f8b504d414a3bf02d7d68e4a) Test that globs match torrent contents - Fixes [#377](https://github.com/casey/intermodal/issues/377) - _Casey Rodarmor <casey@rodarmor.com>_
- :books: [`0a754d0bcfcf`](https://github.com/casey/intermodal/commit/0a754d0bcfcfd65127d7b6e78d41852df78d3ea2) Add manual Arch install link - Fixes [#373](https://github.com/casey/intermodal/issues/373) - _Casey Rodarmor <casey@rodarmor.com>_
- :art: [`0a870ed2ee2c`](https://github.com/casey/intermodal/commit/0a870ed2ee2cca79fddb9940fb879354468deb4d) Get current time early when creating torrents - Fixes [#207](https://github.com/casey/intermodal/issues/207) - _Casey Rodarmor <casey@rodarmor.com>_
- :books: [`9098d3684032`](https://github.com/casey/intermodal/commit/9098d368403232a07684cae8c0b9b1f1383dd2ce) Readme improvements - _Casey Rodarmor <casey@rodarmor.com>_

View File

@ -55,8 +55,9 @@ pub(crate) use crate::{consts, error, host_port_parse_error};
// traits
pub(crate) use crate::{
input_stream::InputStream, into_u64::IntoU64, into_usize::IntoUsize, path_ext::PathExt,
platform_interface::PlatformInterface, print::Print, reckoner::Reckoner, step::Step,
input_stream::InputStream, into_u64::IntoU64, into_usize::IntoUsize, invariant::Invariant,
path_ext::PathExt, platform_interface::PlatformInterface, print::Print, reckoner::Reckoner,
step::Step,
};
// structs and enums

View File

@ -173,7 +173,10 @@ impl From<SystemTimeError> for Error {
impl From<walkdir::Error> for Error {
fn from(walkdir_error: walkdir::Error) -> Self {
let path = walkdir_error.path().unwrap().to_owned();
let path = walkdir_error
.path()
.invariant_unwrap("Walkdir errors always have path")
.to_owned();
if let Some(source) = walkdir_error.into_io_error() {
Self::Filesystem { source, path }

View File

@ -19,11 +19,18 @@ impl FromStr for HostPort {
$
",
)
.unwrap();
.invariant_unwrap("regex is valid");
if let Some(captures) = socket_address_re.captures(text) {
let host_text = captures.name("host").unwrap().as_str();
let port_text = captures.name("port").unwrap().as_str();
let host_text = captures
.name("host")
.invariant_unwrap("Capture group `host` always present")
.as_str();
let port_text = captures
.name("port")
.invariant_unwrap("Capture group `port` always present")
.as_str();
let host = Host::parse(&host_text).context(host_port_parse_error::Host {
text: text.to_owned(),

22
src/invariant.rs Normal file
View File

@ -0,0 +1,22 @@
use crate::common::*;
pub(crate) trait Invariant<T: Sized>: Sized {
fn invariant<D: Display>(self, invariant: D) -> Result<T>;
fn invariant_unwrap<D: Display>(self, invariant: D) -> T {
#![allow(clippy::result_unwrap_used)]
self.invariant(invariant).unwrap()
}
}
impl<T> Invariant<T> for Option<T> {
fn invariant<D: Display>(self, invariant: D) -> Result<T> {
self.ok_or_else(|| Error::internal(format!("Invariant violated: {}", invariant)))
}
}
impl<T, E: std::error::Error> Invariant<T> for Result<T, E> {
fn invariant<D: Display>(self, invariant: D) -> Result<T> {
self.map_err(|err| Error::internal(format!("Invariant `{}` violated: {}", invariant, err)))
}
}

View File

@ -50,7 +50,7 @@ impl MagnetLink {
}
pub(crate) fn to_url(&self) -> Url {
let mut url = Url::parse("magnet:").unwrap();
let mut url = Url::parse("magnet:").invariant_unwrap("`magnet:` is valid URL");
let mut query = format!("xt=urn:btih:{}", self.infohash);

View File

@ -13,9 +13,6 @@
clippy::needless_pass_by_value,
clippy::non_ascii_literal,
clippy::option_map_unwrap_or_else,
clippy::option_unwrap_used,
clippy::result_expect_used,
clippy::result_unwrap_used,
clippy::shadow_reuse,
clippy::too_many_lines,
clippy::unseparated_literal_suffix,
@ -71,6 +68,7 @@ mod input_stream;
mod input_target;
mod into_u64;
mod into_usize;
mod invariant;
mod lint;
mod linter;
mod magnet_link;

View File

@ -67,7 +67,13 @@ impl<'de> Deserialize<'de> for PieceList {
let piece_hashes = bytes
.chunks_exact(Sha1Digest::LENGTH)
.map(|chunk| Sha1Digest::from_bytes(chunk.try_into().unwrap()))
.map(|chunk| {
Sha1Digest::from_bytes(
chunk
.try_into()
.invariant_unwrap("chunks are all Sha1Digest::LENGTH"),
)
})
.collect();
Ok(Self { piece_hashes })

View File

@ -23,7 +23,12 @@ impl PlatformInterface for Platform {
let mut stat: libc::stat = unsafe { mem::zeroed() };
let cpath = CString::new(path.as_os_str().as_bytes()).expect("Path contained null character.");
let cpath = if let Ok(cstr) = CString::new(path.as_os_str().as_bytes()) {
cstr
} else {
// Consider paths containing null bytes to be hidden
return Ok(true);
};
let error_code = unsafe { libc::stat(cpath.as_ptr(), &mut stat) };

View File

@ -128,7 +128,7 @@ struct Extractor {
impl Extractor {
fn new(print: bool, regexes: &[Regex]) -> Self {
let regex_set = RegexSet::new(regexes.iter().map(Regex::as_str))
.expect("Validated regex pattern failed to recompile in regex set");
.invariant_unwrap("Regexes already validated by compilation");
Self {
bencode_decode_errors: 0,

View File

@ -47,7 +47,7 @@ impl Verify {
content.clone()
} else {
match &self.metainfo {
InputTarget::Path(path) => path.parent().unwrap().join(&metainfo.info.name),
InputTarget::Path(path) => path.join("..").join(&metainfo.info.name).clean(),
InputTarget::Stdin => PathBuf::from(&metainfo.info.name),
}
};

View File

@ -62,7 +62,7 @@ impl TorrentSummary {
creation_date
.min(i64::max_value() as u64)
.try_into()
.unwrap(),
.invariant_unwrap("min with i64 is always valid i64"),
0,
),
);

View File

@ -79,7 +79,7 @@ impl<'a> Verifier<'a> {
let to_buffer: usize = remaining
.min(self.buffer.len().into_u64())
.try_into()
.unwrap();
.invariant_unwrap("min with usize should fit in usize");
let buffer = &mut self.buffer[0..to_buffer];