feat(status): add error context
This commit was merged in pull request #70.
This commit is contained in:
@@ -1,5 +1,12 @@
|
|||||||
pub type Result<T> = std::result::Result<T, Status>;
|
pub type Result<T> = std::result::Result<T, Status>;
|
||||||
|
|
||||||
|
pub trait ResultExt<T> {
|
||||||
|
fn with_context<C, F>(self, context: F) -> Result<T>
|
||||||
|
where
|
||||||
|
C: Into<String>,
|
||||||
|
F: FnOnce() -> C;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Code {
|
pub enum Code {
|
||||||
InvalidArgument,
|
InvalidArgument,
|
||||||
@@ -48,6 +55,10 @@ impl Status {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_context(self, context: impl Into<String>) -> Self {
|
||||||
|
Self::new(self.code, context).with_source(self)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn code(&self) -> Code {
|
pub fn code(&self) -> Code {
|
||||||
self.code
|
self.code
|
||||||
}
|
}
|
||||||
@@ -82,3 +93,16 @@ impl From<std::io::Error> for Status {
|
|||||||
Self::new(code, error.to_string()).with_source(error)
|
Self::new(code, error.to_string()).with_source(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T, E> ResultExt<T> for std::result::Result<T, E>
|
||||||
|
where
|
||||||
|
E: Into<Status>,
|
||||||
|
{
|
||||||
|
fn with_context<C, F>(self, context: F) -> Result<T>
|
||||||
|
where
|
||||||
|
C: Into<String>,
|
||||||
|
F: FnOnce() -> C,
|
||||||
|
{
|
||||||
|
self.map_err(|error| error.into().with_context(context()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
use status::{Code, Status};
|
use status::{Code, ResultExt, Status};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn constructors_set_code_and_message() {
|
fn constructors_set_code_and_message() {
|
||||||
@@ -34,6 +34,29 @@ fn with_source_preserves_source_error() {
|
|||||||
assert_eq!("denied", status.source().unwrap().to_string());
|
assert_eq!("denied", status.source().unwrap().to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn with_context_preserves_code_and_wraps_status() {
|
||||||
|
let status = Status::not_found("missing input").with_context("failed to load config");
|
||||||
|
|
||||||
|
assert_eq!(Code::NotFound, status.code());
|
||||||
|
assert_eq!("failed to load config", status.message());
|
||||||
|
assert_eq!("NotFound: missing input", status.source().unwrap().to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn result_ext_adds_lazy_context() {
|
||||||
|
let result: status::Result<()> = Err(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::PermissionDenied,
|
||||||
|
"denied",
|
||||||
|
))
|
||||||
|
.with_context(|| "failed to read input");
|
||||||
|
|
||||||
|
let status = result.unwrap_err();
|
||||||
|
assert_eq!(Code::PermissionDenied, status.code());
|
||||||
|
assert_eq!("failed to read input", status.message());
|
||||||
|
assert_eq!("PermissionDenied: denied", status.source().unwrap().to_string());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn io_error_maps_not_found() {
|
fn io_error_maps_not_found() {
|
||||||
let status = Status::from(std::io::Error::new(
|
let status = Status::from(std::io::Error::new(
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use status::ResultExt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use xatu::SourceFile;
|
use xatu::SourceFile;
|
||||||
|
|
||||||
@@ -26,7 +27,8 @@ fn is_excluded_dir(path: &Path) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn collect_source_files(path: &Path) -> status::Result<Vec<SourceFile>> {
|
fn collect_source_files(path: &Path) -> status::Result<Vec<SourceFile>> {
|
||||||
let metadata = std::fs::symlink_metadata(path)?;
|
let metadata = std::fs::symlink_metadata(path)
|
||||||
|
.with_context(|| format!("failed to read metadata for {}", path.display()))?;
|
||||||
|
|
||||||
if metadata.file_type().is_dir() {
|
if metadata.file_type().is_dir() {
|
||||||
if is_excluded_dir(path) {
|
if is_excluded_dir(path) {
|
||||||
@@ -35,8 +37,11 @@ fn collect_source_files(path: &Path) -> status::Result<Vec<SourceFile>> {
|
|||||||
|
|
||||||
let mut files = Vec::new();
|
let mut files = Vec::new();
|
||||||
|
|
||||||
for entry in std::fs::read_dir(path)? {
|
for entry in std::fs::read_dir(path)
|
||||||
let entry = entry?;
|
.with_context(|| format!("failed to read directory {}", path.display()))?
|
||||||
|
{
|
||||||
|
let entry =
|
||||||
|
entry.with_context(|| format!("failed to read entry in {}", path.display()))?;
|
||||||
files.extend(collect_source_files(&entry.path())?);
|
files.extend(collect_source_files(&entry.path())?);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +51,8 @@ fn collect_source_files(path: &Path) -> status::Result<Vec<SourceFile>> {
|
|||||||
if metadata.file_type().is_file() && is_nix_file(path) {
|
if metadata.file_type().is_file() && is_nix_file(path) {
|
||||||
return Ok(vec![SourceFile {
|
return Ok(vec![SourceFile {
|
||||||
path: path.to_path_buf(),
|
path: path.to_path_buf(),
|
||||||
source: std::fs::read_to_string(path)?,
|
source: std::fs::read_to_string(path)
|
||||||
|
.with_context(|| format!("failed to read source file {}", path.display()))?,
|
||||||
}]);
|
}]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user