how.rs/src/context.rs

96 lines
2.3 KiB
Rust

use std::borrow::Cow;
/// Provides context furthering the explanation of *how* you got to an error.
#[derive(Debug, Clone)]
pub struct Context(ContextInner);
impl std::fmt::Display for Context {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.0 {
ContextInner::String(ref s) => f.write_str(s),
ContextInner::Compound(ref ctxs) => {
let mut ctxs = ctxs.iter();
if let Some(ctx) = ctxs.next() {
write!(f, "{ctx}")?;
for ctx in ctxs {
write!(f, "\n{ctx}")?;
}
}
Ok(())
}
}
}
}
#[derive(Debug, Clone)]
enum ContextInner {
String(Cow<'static, str>),
Compound(Vec<Context>),
}
pub trait IntoContext {
fn into_context(self) -> Context;
#[inline(always)]
fn chain(self, other: impl IntoContext) -> Context
where
Self: Sized,
{
self.into_context().chain(other)
}
}
impl IntoContext for Context {
#[inline(always)]
fn into_context(self) -> Context {
self
}
/// Chains another piece of context that is equal from a hierarchical perspective.
// TODO: should this inline? Would the compiler be allowed to fold the allocations into a
// single one with inlining?
fn chain(self, other: impl IntoContext) -> Self {
let items = match self {
Context(ContextInner::Compound(mut items)) => {
items.push(other.into_context());
items
}
_ => vec![self, other.into_context()],
};
Context(ContextInner::Compound(items))
}
}
impl IntoContext for String {
#[inline]
fn into_context(self) -> Context {
Context(ContextInner::String(self.into()))
}
}
impl IntoContext for &'static str {
#[inline]
fn into_context(self) -> Context {
Context(ContextInner::String(self.into()))
}
}
impl IntoContext for Cow<'static, str> {
// TODO: should this always inline?
#[inline]
fn into_context(self) -> Context {
Context(ContextInner::String(self))
}
}
impl<C, F> IntoContext for F
where
C: IntoContext,
F: FnOnce() -> C,
{
#[inline(always)]
fn into_context(self) -> Context {
self().into_context()
}
}