Refactoring, update docs, API adjustments

This commit is contained in:
Michael Pfaff 2023-06-29 01:51:08 -04:00
parent 9950d96522
commit efa1c3fa37
Signed by: michael
GPG Key ID: CF402C4A012AA9D4
8 changed files with 100 additions and 125 deletions

View File

@ -11,6 +11,7 @@ termination = ["dep:ansee"]
[dependencies]
ansee = { git = "https://git.pfaff.dev/michael/ansee", optional = true }
typeid-cast = "0.1"
[[example]]
name = "output"

View File

@ -1,19 +1,40 @@
# *How*
A seriously contextual error library that focuses on how you got there. Designed to make debugging parser logic easier, *how* enables you to concisely capture any and all context that could possibly have contributed to an error.
A seriously contextual error library that focuses on how you got there. Designed to make debugging easier, *how* enables you to concisely capture any and all context that could possibly have contributed to an error.
## Getting started
Thanks to *how*'s minimal set of public types whose names are all unique from those of most crates (aside from other error handling libraries), you can safely use star imports anywhere you want to use *how*.
A basic example takes just 2 imports:
```rust,should_panic
use how::*;
use how::{How, Result};
fn main() -> Result<()> {
Err(How::new("TODO: implement amazing new program"))
}
```
[`How`] intentionally omits a [`From`] implementation for [`std::error::Error`] to discourage the creation of [`How`]s with no caller context. Instead, the [`Explain`] trait is implemented for all `Result`[^2] and provides a convenient [`context`](Explain::context) function.
But usually you'll want to [attach some information](Explain::context) too:
```rust,should_panic
use how::{How, Explain, Result};
[^2]: Where `E` is either [`How`] or implements [`std::error::Error`]. Errors that don't implement [`std::error::Error`] (usually in order to be permitted to implement [`From`] for any type that implements [`std::error::Error`]) can only be, are not, and will not be, supported manually.
fn main() -> Result<()> {
Err(How::new("TODO: implement amazing new program")
.context("I plan to do it eventually™"))
}
```
And you'll probably want to get [nicer output](TerminationResult) when you return an error from `main` (this one requires `feature = "termination"`):
```rust,should_panic,compile_fail
use how::{How, Explain, Result, TerminationResult};
fn main() -> TerminationResult<()> {
Err(How::new("TODO: implement amazing new program")
.context("I plan to do it eventually™"))
.into()
}
```
[`How`] intentionally omits a [`From`] implementation for [`Error`](std::error::Error) to discourage the creation of [`How`]s with no caller context. Instead, the [`Explain`] trait is implemented for all [`Result`][^1] and [`Option`] and provides a convenient [`context`](Explain::context) function.
[^1]: Where `E` is either [`How`] or implements [`Error + 'static`](std::error::Error).

View File

@ -1,30 +1,63 @@
use crate::{How, IntoContext, IntoResultHow};
use std::panic::Location;
crate::seal!(pub(crate) private::Sealed);
use crate::{How, IntoContext};
pub trait Explain: Sealed {
pub trait Explain {
type Output;
#[track_caller]
#[must_use]
fn context(self, context: impl IntoContext) -> Self::Output;
}
impl<T, E> Sealed for Result<T, E> where Result<T, E>: IntoResultHow {}
impl<T> Sealed for Option<T> where Option<T>: IntoResultHow {}
impl Explain for How {
type Output = Self;
#[track_caller]
#[inline]
fn context(mut self, context: impl IntoContext) -> Self {
use crate::context::*;
let mut context = context.into_context();
let loc = ContextElem::Location(*Location::caller());
match context.0 {
ContextInner::Elem(elem) => {
context.0 = ContextInner::Compound(vec![elem, loc]);
}
ContextInner::Compound(ref mut vec) => {
vec.insert(1, loc);
}
}
self.push_context(context);
self
}
}
impl<T, E> Explain for Result<T, E>
where
Result<T, E>: IntoResultHow,
E: std::error::Error + 'static,
{
type Output = Result<<Self as IntoResultHow>::T, How>;
type Output = Result<T, How>;
#[inline(always)]
#[track_caller]
fn context(self, context: impl IntoContext) -> Self::Output {
self.into_result_how().map_err(
#[inline(never)]
move |e| e.context(context),
)
#[cold]
#[track_caller]
fn into_and_context<E, C>(e: E, c: C) -> How
where
E: std::error::Error + 'static,
C: IntoContext,
{
match typeid_cast::cast(e) {
Ok(e) => e,
Err(e) => How::new(e.to_string()),
}
.context(c)
}
match self {
Ok(t) => Ok(t),
Err(e) => Err(into_and_context(e, context)),
}
}
}

View File

@ -6,7 +6,7 @@ use crate::*;
use crate::report::report_write;
/// Does not implement [`std::error::Error`] to allow a [`From`] implementation for all other error types.
/// The error type.
///
/// By default, does not implement [`Clone`] because [`std::backtrace::Backtrace`] does not
/// implement [`Clone`]. However, the `clone-with-caveats` feature may be used to enable a
@ -50,7 +50,7 @@ impl How {
}
},
}))
.context(context)
.context(context)
}
#[must_use]
@ -107,6 +107,10 @@ impl How {
let b = b.field("backtrace", &self.0.backtrace);
b.finish()
}
pub(crate) fn push_context(&mut self, context: Context) {
self.0.context.push(context);
}
}
#[cfg(feature = "clone-with-caveats")]
@ -126,30 +130,7 @@ impl Clone for How {
}
}
impl explain::Sealed for How {}
impl Explain for How {
type Output = Self;
#[inline(always)]
#[track_caller]
#[must_use]
fn context(mut self, context: impl IntoContext) -> Self {
use context::*;
let mut context = context.into_context();
let loc = ContextElem::Location(*Location::caller());
match context.0 {
ContextInner::Elem(elem) => {
context.0 = ContextInner::Compound(vec![elem, loc]);
}
ContextInner::Compound(ref mut vec) => {
vec.insert(1, loc);
}
}
self.0.context.push(context);
self
}
}
impl std::error::Error for How {}
impl std::fmt::Display for How {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

View File

@ -1,62 +0,0 @@
use crate::How;
mod private {
use crate::How;
#[doc(hidden)]
pub trait IntoResultHow: Sized {
type T;
fn into_result_how(self) -> Result<Self::T, How>;
}
}
pub(crate) use private::IntoResultHow;
impl<T, E> IntoResultHow for Result<T, E>
where
E: std::error::Error,
{
type T = T;
#[inline(always)]
#[track_caller]
fn into_result_how(self) -> Result<Self::T, How> {
#[inline(never)]
#[track_caller]
fn into<E: std::error::Error>(e: E) -> How {
How::new(e.to_string())
}
match self {
Ok(t) => Ok(t),
Err(e) => Err(into(e)),
}
}
}
impl<T> IntoResultHow for Result<T, How> {
type T = T;
#[inline(always)]
fn into_result_how(self) -> Result<Self::T, How> {
self
}
}
impl<T> IntoResultHow for Option<T> {
type T = T;
#[inline(always)]
#[track_caller]
fn into_result_how(self) -> Result<Self::T, How> {
#[inline(never)]
#[track_caller]
fn into() -> How {
How::new("Option::None")
}
match self {
Some(t) => Ok(t),
None => Err(into()),
}
}
}

View File

@ -1,8 +1,8 @@
#![doc = include_str!("../README.md")]
#![deny(unsafe_code)]
mod sealed;
pub(crate) use sealed::seal;
#![feature(auto_traits)]
#![feature(doc_auto_cfg)]
#![feature(negative_impls)]
mod context;
pub use context::{Context, IntoContext};
@ -10,9 +10,6 @@ pub use context::{Context, IntoContext};
mod report;
pub use report::{Report, ReportOpts};
mod into;
pub(crate) use into::IntoResultHow;
mod explain;
pub use explain::Explain;
@ -25,3 +22,6 @@ mod how;
pub use self::how::How;
pub type Result<T, E = How> = std::result::Result<T, E>;
#[cfg(test)]
mod test;

View File

@ -1,15 +0,0 @@
/// Like [sealed](https://crates.io/crates/sealed) but not a procmacro.
#[doc(hidden)]
#[macro_export]
macro_rules! __sealed__seal {
($vis:vis $mod:ident::$trait:ident$(<$($gen:ident),+>)?) => {
#[doc(hidden)]
mod $mod {
#[doc(hidden)]
pub trait $trait$(<$($gen),+>)? {}
}
#[doc(hidden)]
$vis use $mod::$trait;
};
}
pub use __sealed__seal as seal;

16
src/test.rs Normal file
View File

@ -0,0 +1,16 @@
use super::*;
#[test]
fn test_io_result_context() {
let r: Result<()> =
Err(std::io::Error::new(std::io::ErrorKind::Other, "foo error")).context("bar reason");
assert!(r.is_err());
}
#[test]
fn test_option_context() {
let r = Some(69).context("not nice");
assert!(r.is_ok());
let r = None::<i32>.context("not nice");
assert!(r.is_err());
}