Overhaul error handling
This commit is contained in:
parent
028c72cac4
commit
2bf19e0ea2
4 changed files with 48 additions and 35 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
69
src/tokio.rs
69
src/tokio.rs
|
|
@ -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.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue