migrate from anyhow to thiserror (#197)

* migrate from anyhow to thiserror (#190). pending polish error msgs

* Add backtrace and compartmentalize errors

- Include backtraces in the errors we generate.  To get this we can't
  just return a literal enum, because the backtrace requires a call.
- Related to the previous point: add methods to create errors so
  we can include the backtrace conveniently without changing too much
  the syntax.  So instead of `Err(Error::KeyNotFound(key))` (literal
  enum) it will be `Err(Error::key_not_found(key))` (method call)
- Each error should be local to its scope, and each scope should
  only return its own error.
  - The merkle tree should return `TreeError` and not Error
  - The middleware should return `MiddlewareError` and not Error
- With a global Error we can't easily include backend/frontend types in
  the error fields, so declare a `BackendError` and a `FrontendError`
  and follow the pattern from the previous point
- The Pod traits should be able to return backend errors and will be
  used in the frontend; for that we change them to use trait object
  Error: `dyn std::error::Error`

* fix error

* apply suggestions from @arnaucube

* rename XError and XResult to Error and Result

* reorg signature

* make frontend custom error more ergonomic

* remove unnecessary feature

---------

Co-authored-by: Eduard S. <eduardsanou@posteo.net>
This commit is contained in:
arnaucube 2025-04-22 15:07:04 +02:00 committed by GitHub
parent 58d3c6a236
commit 29545f03fc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 696 additions and 273 deletions

View file

@ -0,0 +1,79 @@
//! tree errors
use std::{backtrace::Backtrace, fmt::Debug};
pub type TreeResult<T, E = TreeError> = core::result::Result<T, E>;
#[derive(Debug, thiserror::Error)]
pub enum TreeInnerError {
#[error("key not found")]
KeyNotFound,
#[error("key already exists")]
KeyExists,
#[error("max depth reached")]
MaxDepth,
#[error("reached empty node, should not have entered")]
EmptyNode,
#[error("proof of {0} does not verify")]
ProofFail(String), // inclusion / exclusion
#[error("invalid {0} proof")]
InvalidProof(String),
#[error("key too short (key length: {0}) for the max_depth: {1}")]
TooShortKey(usize, usize),
}
#[derive(thiserror::Error)]
pub enum TreeError {
#[error("Inner: {inner}\n{backtrace}")]
Inner {
inner: Box<TreeInnerError>,
backtrace: Box<Backtrace>,
},
#[error("anyhow::Error: {0}")]
Anyhow(#[from] anyhow::Error),
}
impl Debug for TreeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self, f)
}
}
macro_rules! new {
($inner:expr) => {
TreeError::Inner {
inner: Box::new($inner),
backtrace: Box::new(Backtrace::capture()),
}
};
}
use TreeInnerError::*;
impl TreeError {
pub fn inner(&self) -> Option<&TreeInnerError> {
match self {
Self::Inner { inner, .. } => Some(inner),
_ => None,
}
}
pub(crate) fn key_not_found() -> Self {
new!(KeyNotFound)
}
pub(crate) fn key_exists() -> Self {
new!(KeyExists)
}
pub(crate) fn max_depth() -> Self {
new!(MaxDepth)
}
pub(crate) fn empty_node() -> Self {
new!(EmptyNode)
}
pub(crate) fn proof_fail(obj: String) -> Self {
new!(ProofFail(obj))
}
pub(crate) fn invalid_proof(obj: String) -> Self {
new!(InvalidProof(obj))
}
pub(crate) fn too_short_key(depth: usize, max_depth: usize) -> Self {
new!(TooShortKey(depth, max_depth))
}
}