Overhaul error handling

This commit is contained in:
Joscha 2023-03-18 13:36:48 +01:00
parent 028c72cac4
commit 2bf19e0ea2
4 changed files with 48 additions and 35 deletions

View file

@ -13,6 +13,11 @@ Procedure when bumping the version number:
## Unreleased ## Unreleased
### Changed
- Error handling of `Action`s is now more complex but more powerful. In
particular, `Action`s can now return almost arbitrary errors without nesting
`Result`s like before.
## v0.1.0 - 2023-02-12 ## v0.1.0 - 2023-02-12
Initial release Initial release

View file

@ -24,7 +24,8 @@ use rusqlite::{Connection, Transaction};
/// return the result. The way in which this occurs depends on the vault. /// return the result. The way in which this occurs depends on the vault.
pub trait Action { pub trait Action {
type Result; type Result;
fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result>; type Error;
fn run(self, conn: &mut Connection) -> Result<Self::Result, Self::Error>;
} }
/// A single database migration. /// A single database migration.

View file

@ -52,11 +52,7 @@ impl SimpleVault {
} }
/// Execute an [`Action`] and return the result. /// Execute an [`Action`] and return the result.
pub fn execute<A>(&mut self, action: A) -> rusqlite::Result<A::Result> pub fn execute<A: Action>(&mut self, action: A) -> Result<A::Result, A::Error> {
where
A: Action + Send + 'static,
A::Result: Send,
{
action.run(&mut self.0) action.run(&mut self.0)
} }
} }

View file

@ -1,6 +1,6 @@
//! A vault for use with [`tokio`]. //! A vault for use with [`tokio`].
use std::{any::Any, error, fmt, result, thread}; use std::{any::Any, error, fmt, thread};
use rusqlite::Connection; use rusqlite::Connection;
use tokio::sync::{mpsc, oneshot}; use tokio::sync::{mpsc, oneshot};
@ -12,16 +12,25 @@ use crate::{Action, Migration};
/// ///
/// This way, the trait that users of this crate interact with is kept simpler. /// This way, the trait that users of this crate interact with is kept simpler.
trait ActionWrapper { trait ActionWrapper {
fn run(self: Box<Self>, conn: &mut Connection) -> rusqlite::Result<Box<dyn Any + Send>>; fn run(
self: Box<Self>,
conn: &mut Connection,
) -> Result<Box<dyn Any + Send>, Box<dyn Any + Send>>;
} }
impl<T: Action> ActionWrapper for T impl<T: Action> ActionWrapper for T
where where
T::Result: Send + 'static, T::Result: Send + 'static,
T::Error: Send + 'static,
{ {
fn run(self: Box<Self>, conn: &mut Connection) -> rusqlite::Result<Box<dyn Any + Send>> { fn run(
let result = (*self).run(conn)?; self: Box<Self>,
Ok(Box::new(result)) conn: &mut Connection,
) -> Result<Box<dyn Any + Send>, Box<dyn Any + Send>> {
match (*self).run(conn) {
Ok(result) => Ok(Box::new(result)),
Err(err) => Err(Box::new(err)),
}
} }
} }
@ -29,46 +38,38 @@ where
enum Command { enum Command {
Action( Action(
Box<dyn ActionWrapper + Send>, Box<dyn ActionWrapper + Send>,
oneshot::Sender<rusqlite::Result<Box<dyn Any + Send>>>, oneshot::Sender<Result<Box<dyn Any + Send>, Box<dyn Any + Send>>>,
), ),
Stop(oneshot::Sender<()>), Stop(oneshot::Sender<()>),
} }
/// Error that can occur during execution of an [`Action`]. /// Error that can occur during execution of an [`Action`].
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error<E> {
/// The vault's thread has been stopped and its sqlite connection closed. /// The vault's thread has been stopped and its sqlite connection closed.
Stopped, Stopped,
/// A [`rusqlite::Error`] occurred while running the action. /// An error was returned by the [`Action`].
Rusqlite(rusqlite::Error), Action(E),
} }
impl fmt::Display for Error { impl<E: fmt::Display> fmt::Display for Error<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::Stopped => "vault has been stopped".fmt(f), Self::Stopped => "vault has been stopped".fmt(f),
Self::Rusqlite(err) => err.fmt(f), Self::Action(err) => err.fmt(f),
} }
} }
} }
impl error::Error for Error { impl<E: error::Error> error::Error for Error<E> {
fn source(&self) -> Option<&(dyn error::Error + 'static)> { fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self { match self {
Self::Stopped => None, Self::Stopped => None,
Self::Rusqlite(err) => Some(err), Self::Action(err) => err.source(),
} }
} }
} }
impl From<rusqlite::Error> for Error {
fn from(value: rusqlite::Error) -> Self {
Self::Rusqlite(value)
}
}
pub type Result<R> = result::Result<R, Error>;
fn run(mut conn: Connection, mut rx: mpsc::UnboundedReceiver<Command>) { fn run(mut conn: Connection, mut rx: mpsc::UnboundedReceiver<Command>) {
while let Some(command) = rx.blocking_recv() { while let Some(command) = rx.blocking_recv() {
match command { match command {
@ -132,24 +133,34 @@ impl TokioVault {
} }
/// Execute an [`Action`] and return the result. /// Execute an [`Action`] and return the result.
pub async fn execute<A>(&self, action: A) -> Result<A::Result> pub async fn execute<A>(&self, action: A) -> Result<A::Result, Error<A::Error>>
where where
A: Action + Send + 'static, A: Action + Send + 'static,
A::Result: Send, A::Result: Send,
A::Error: Send,
{ {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
self.tx self.tx
.send(Command::Action(Box::new(action), tx)) .send(Command::Action(Box::new(action), tx))
.map_err(|_| Error::Stopped)?; .map_err(|_| Error::Stopped)?;
let result = rx.await.map_err(|_| Error::Stopped)??; let result = rx.await.map_err(|_| Error::Stopped)?;
// The ActionWrapper runs Action::run, which returns Action::Result. It // The ActionWrapper runs Action::run, which returns
// then wraps this into Any, which we're now trying to downcast again to // Result<Action::Result, Action::Error>. It then wraps the
// Action::Result. This should always work. // Action::Result and Action::Error into Any, which we're now trying to
let result = result.downcast().unwrap(); // downcast again to Action::Result and Action::Error. This should
// always work.
Ok(*result) match result {
Ok(result) => {
let result = *result.downcast::<A::Result>().unwrap();
Ok(result)
}
Err(err) => {
let err = *err.downcast::<A::Error>().unwrap();
Err(Error::Action(err))
}
}
} }
/// Stop the vault's thread and close its sqlite connection. /// Stop the vault's thread and close its sqlite connection.