Compare commits
No commits in common. "master" and "v0.1.0" have entirely different histories.
6 changed files with 42 additions and 422 deletions
40
CHANGELOG.md
40
CHANGELOG.md
|
|
@ -4,7 +4,6 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
Procedure when bumping the version number:
|
Procedure when bumping the version number:
|
||||||
|
|
||||||
1. Update dependencies in a separate commit
|
1. Update dependencies in a separate commit
|
||||||
2. Set version number in `Cargo.toml`
|
2. Set version number in `Cargo.toml`
|
||||||
3. Add new section in this changelog
|
3. Add new section in this changelog
|
||||||
|
|
@ -14,45 +13,6 @@ Procedure when bumping the version number:
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- **(breaking)** Bumped `rusqlite` dependency from `0.32` to `0.33`
|
|
||||||
|
|
||||||
## v0.5.0 - 2024-09-04
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- **(breaking)** Bumped `rusqlite` dependency from `0.31` to `0.32`
|
|
||||||
|
|
||||||
## v0.4.0 - 2024-02-23
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- **(breaking)** Bumped `rusqlite` dependency from `0.30` to `0.31`
|
|
||||||
|
|
||||||
## v0.3.0 - 2023-12-26
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- **(breaking)** Bumped `rusqlite` dependency from `0.29` to `0.30`
|
|
||||||
|
|
||||||
## v0.2.0 - 2023-05-14
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- `serde` feature
|
|
||||||
- `serde::from_row_via_index`
|
|
||||||
- `serde::from_row_via_name`
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- **(breaking)**
|
|
||||||
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.
|
|
||||||
- **(breaking)** Renamed `Action::Result` to `Action::Output`
|
|
||||||
- **(breaking)** Bumped `rusqlite` dependency from `0.28` to `0.29`
|
|
||||||
|
|
||||||
## v0.1.0 - 2023-02-12
|
## v0.1.0 - 2023-02-12
|
||||||
|
|
||||||
Initial release
|
Initial release
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
[package]
|
[package]
|
||||||
name = "vault"
|
name = "vault"
|
||||||
version = "0.5.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
serde = ["dep:serde"]
|
|
||||||
tokio = ["dep:tokio"]
|
tokio = ["dep:tokio"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rusqlite = "0.33.0"
|
rusqlite = "0.28.0"
|
||||||
serde = { version = "1.0.209", optional = true }
|
tokio = { version = "1.25.0", features = ["sync"], optional = true }
|
||||||
tokio = { version = "1.40.0", features = ["sync"], optional = true }
|
|
||||||
|
|
|
||||||
10
src/lib.rs
10
src/lib.rs
|
|
@ -9,17 +9,12 @@
|
||||||
// Clippy lints
|
// Clippy lints
|
||||||
#![warn(clippy::use_self)]
|
#![warn(clippy::use_self)]
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
pub mod serde;
|
|
||||||
pub mod simple;
|
pub mod simple;
|
||||||
#[cfg(feature = "tokio")]
|
#[cfg(feature = "tokio")]
|
||||||
pub mod tokio;
|
pub mod tokio;
|
||||||
|
|
||||||
use rusqlite::{Connection, Transaction};
|
use rusqlite::{Connection, Transaction};
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
pub use self::serde::*;
|
|
||||||
|
|
||||||
/// An action that can be performed on a [`Connection`].
|
/// An action that can be performed on a [`Connection`].
|
||||||
///
|
///
|
||||||
/// Both commands and queries are considered actions. Commands usually have a
|
/// Both commands and queries are considered actions. Commands usually have a
|
||||||
|
|
@ -28,9 +23,8 @@ pub use self::serde::*;
|
||||||
/// Actions are usually passed to a vault which will then execute them and
|
/// Actions are usually passed to a vault which will then execute them and
|
||||||
/// 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 Output;
|
type Result;
|
||||||
type Error;
|
fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result>;
|
||||||
fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A single database migration.
|
/// A single database migration.
|
||||||
|
|
|
||||||
325
src/serde.rs
325
src/serde.rs
|
|
@ -1,325 +0,0 @@
|
||||||
use std::{error, fmt, str::Utf8Error};
|
|
||||||
|
|
||||||
use rusqlite::{
|
|
||||||
types::{FromSqlError, ValueRef},
|
|
||||||
Row,
|
|
||||||
};
|
|
||||||
use serde::{
|
|
||||||
de::{
|
|
||||||
self, value::BorrowedStrDeserializer, DeserializeSeed, Deserializer, MapAccess, SeqAccess,
|
|
||||||
Visitor,
|
|
||||||
},
|
|
||||||
forward_to_deserialize_any, Deserialize,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum Error {
|
|
||||||
ExpectedTupleLikeBaseType,
|
|
||||||
ExpectedStructLikeBaseType,
|
|
||||||
Utf8(Utf8Error),
|
|
||||||
Rusqlite(rusqlite::Error),
|
|
||||||
Custom(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::ExpectedTupleLikeBaseType => write!(f, "expected tuple-like base type"),
|
|
||||||
Self::ExpectedStructLikeBaseType => write!(f, "expected struct-like base type"),
|
|
||||||
Self::Utf8(err) => err.fmt(f),
|
|
||||||
Self::Rusqlite(err) => err.fmt(f),
|
|
||||||
Self::Custom(msg) => msg.fmt(f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl error::Error for Error {}
|
|
||||||
|
|
||||||
impl de::Error for Error {
|
|
||||||
fn custom<T: fmt::Display>(msg: T) -> Self {
|
|
||||||
Self::Custom(msg.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Utf8Error> for Error {
|
|
||||||
fn from(value: Utf8Error) -> Self {
|
|
||||||
Self::Utf8(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<rusqlite::Error> for Error {
|
|
||||||
fn from(value: rusqlite::Error) -> Self {
|
|
||||||
Self::Rusqlite(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ValueRefDeserializer<'de> {
|
|
||||||
value: ValueRef<'de>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserializer<'de> for ValueRefDeserializer<'de> {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
forward_to_deserialize_any! {
|
|
||||||
i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes byte_buf
|
|
||||||
unit unit_struct seq tuple tuple_struct map struct identifier
|
|
||||||
ignored_any
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
|
|
||||||
match self.value {
|
|
||||||
ValueRef::Null => visitor.visit_unit(),
|
|
||||||
ValueRef::Integer(v) => visitor.visit_i64(v),
|
|
||||||
ValueRef::Real(v) => visitor.visit_f64(v),
|
|
||||||
ValueRef::Text(v) => visitor.visit_borrowed_str(std::str::from_utf8(v)?),
|
|
||||||
ValueRef::Blob(v) => visitor.visit_borrowed_bytes(v),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_bool<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
|
|
||||||
match self.value {
|
|
||||||
ValueRef::Integer(0) => visitor.visit_bool(false),
|
|
||||||
ValueRef::Integer(_) => visitor.visit_bool(true),
|
|
||||||
_ => self.deserialize_any(visitor),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_option<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
|
|
||||||
match self.value {
|
|
||||||
ValueRef::Null => visitor.visit_none(),
|
|
||||||
_ => visitor.visit_some(self),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_newtype_struct<V: Visitor<'de>>(
|
|
||||||
self,
|
|
||||||
_name: &'static str,
|
|
||||||
visitor: V,
|
|
||||||
) -> Result<V::Value, Self::Error> {
|
|
||||||
visitor.visit_newtype_struct(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_enum<V: Visitor<'de>>(
|
|
||||||
self,
|
|
||||||
name: &'static str,
|
|
||||||
variants: &'static [&'static str],
|
|
||||||
visitor: V,
|
|
||||||
) -> Result<V::Value, Self::Error> {
|
|
||||||
match self.value {
|
|
||||||
ValueRef::Text(v) => {
|
|
||||||
let v = BorrowedStrDeserializer::new(std::str::from_utf8(v)?);
|
|
||||||
v.deserialize_enum(name, variants, visitor)
|
|
||||||
}
|
|
||||||
_ => self.deserialize_any(visitor),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct IndexedRowDeserializer<'de, 'stmt> {
|
|
||||||
row: &'de Row<'stmt>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserializer<'de> for IndexedRowDeserializer<'de, '_> {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
forward_to_deserialize_any! {
|
|
||||||
bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes
|
|
||||||
byte_buf option unit unit_struct map enum identifier ignored_any
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_any<V: Visitor<'de>>(self, _visitor: V) -> Result<V::Value, Self::Error> {
|
|
||||||
Err(Error::ExpectedTupleLikeBaseType)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_newtype_struct<V: Visitor<'de>>(
|
|
||||||
self,
|
|
||||||
_name: &'static str,
|
|
||||||
visitor: V,
|
|
||||||
) -> Result<V::Value, Self::Error> {
|
|
||||||
visitor.visit_newtype_struct(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_seq<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
|
|
||||||
visitor.visit_seq(IndexedRowSeq::new(self.row))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_tuple<V: Visitor<'de>>(
|
|
||||||
self,
|
|
||||||
_len: usize,
|
|
||||||
visitor: V,
|
|
||||||
) -> Result<V::Value, Self::Error> {
|
|
||||||
self.deserialize_seq(visitor)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_tuple_struct<V: Visitor<'de>>(
|
|
||||||
self,
|
|
||||||
_name: &'static str,
|
|
||||||
_len: usize,
|
|
||||||
visitor: V,
|
|
||||||
) -> Result<V::Value, Self::Error> {
|
|
||||||
self.deserialize_seq(visitor)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_struct<V: Visitor<'de>>(
|
|
||||||
self,
|
|
||||||
_name: &'static str,
|
|
||||||
fields: &'static [&'static str],
|
|
||||||
visitor: V,
|
|
||||||
) -> Result<V::Value, Self::Error> {
|
|
||||||
visitor.visit_map(IndexedRowMap::new(self.row, fields))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct IndexedRowSeq<'de, 'stmt> {
|
|
||||||
row: &'de Row<'stmt>,
|
|
||||||
next_index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de, 'stmt> IndexedRowSeq<'de, 'stmt> {
|
|
||||||
fn new(row: &'de Row<'stmt>) -> Self {
|
|
||||||
Self { row, next_index: 0 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> SeqAccess<'de> for IndexedRowSeq<'de, '_> {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, Self::Error>
|
|
||||||
where
|
|
||||||
T: DeserializeSeed<'de>,
|
|
||||||
{
|
|
||||||
match self.row.get_ref(self.next_index) {
|
|
||||||
Ok(value) => {
|
|
||||||
self.next_index += 1;
|
|
||||||
seed.deserialize(ValueRefDeserializer { value }).map(Some)
|
|
||||||
}
|
|
||||||
Err(rusqlite::Error::InvalidColumnIndex(_)) => Ok(None),
|
|
||||||
Err(err) => Err(err)?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct IndexedRowMap<'de, 'stmt> {
|
|
||||||
row: &'de Row<'stmt>,
|
|
||||||
fields: &'static [&'static str],
|
|
||||||
next_index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de, 'stmt> IndexedRowMap<'de, 'stmt> {
|
|
||||||
fn new(row: &'de Row<'stmt>, fields: &'static [&'static str]) -> Self {
|
|
||||||
Self {
|
|
||||||
row,
|
|
||||||
fields,
|
|
||||||
next_index: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> MapAccess<'de> for IndexedRowMap<'de, '_> {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error>
|
|
||||||
where
|
|
||||||
K: DeserializeSeed<'de>,
|
|
||||||
{
|
|
||||||
if let Some(key) = self.fields.get(self.next_index) {
|
|
||||||
self.next_index += 1;
|
|
||||||
seed.deserialize(BorrowedStrDeserializer::new(key))
|
|
||||||
.map(Some)
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: DeserializeSeed<'de>,
|
|
||||||
{
|
|
||||||
let value = self.row.get_ref(self.next_index - 1)?;
|
|
||||||
seed.deserialize(ValueRefDeserializer { value })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_row_via_index<'de, T>(row: &'de Row<'_>) -> rusqlite::Result<T>
|
|
||||||
where
|
|
||||||
T: Deserialize<'de>,
|
|
||||||
{
|
|
||||||
T::deserialize(IndexedRowDeserializer { row })
|
|
||||||
.map_err(|err| FromSqlError::Other(Box::new(err)).into())
|
|
||||||
}
|
|
||||||
|
|
||||||
struct NamedRowDeserializer<'de, 'stmt> {
|
|
||||||
row: &'de Row<'stmt>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserializer<'de> for NamedRowDeserializer<'de, '_> {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
forward_to_deserialize_any! {
|
|
||||||
bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes
|
|
||||||
byte_buf option unit unit_struct newtype_struct seq tuple tuple_struct
|
|
||||||
map enum identifier ignored_any
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_any<V: Visitor<'de>>(self, _visitor: V) -> Result<V::Value, Self::Error> {
|
|
||||||
Err(Error::ExpectedStructLikeBaseType)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_struct<V: Visitor<'de>>(
|
|
||||||
self,
|
|
||||||
_name: &'static str,
|
|
||||||
fields: &'static [&'static str],
|
|
||||||
visitor: V,
|
|
||||||
) -> Result<V::Value, Self::Error> {
|
|
||||||
visitor.visit_map(NamedRowMap::new(self.row, fields))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct NamedRowMap<'de, 'stmt> {
|
|
||||||
row: &'de Row<'stmt>,
|
|
||||||
fields: &'static [&'static str],
|
|
||||||
next_index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de, 'stmt> NamedRowMap<'de, 'stmt> {
|
|
||||||
fn new(row: &'de Row<'stmt>, fields: &'static [&'static str]) -> Self {
|
|
||||||
Self {
|
|
||||||
row,
|
|
||||||
fields,
|
|
||||||
next_index: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> MapAccess<'de> for NamedRowMap<'de, '_> {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error>
|
|
||||||
where
|
|
||||||
K: DeserializeSeed<'de>,
|
|
||||||
{
|
|
||||||
if let Some(key) = self.fields.get(self.next_index) {
|
|
||||||
self.next_index += 1;
|
|
||||||
seed.deserialize(BorrowedStrDeserializer::new(key))
|
|
||||||
.map(Some)
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: DeserializeSeed<'de>,
|
|
||||||
{
|
|
||||||
let value = self.row.get_ref(self.next_index - 1)?;
|
|
||||||
seed.deserialize(ValueRefDeserializer { value })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_row_via_name<'de, T>(row: &'de Row<'_>) -> rusqlite::Result<T>
|
|
||||||
where
|
|
||||||
T: Deserialize<'de>,
|
|
||||||
{
|
|
||||||
T::deserialize(NamedRowDeserializer { row })
|
|
||||||
.map_err(|err| FromSqlError::Other(Box::new(err)).into())
|
|
||||||
}
|
|
||||||
|
|
@ -34,13 +34,13 @@ impl SimpleVault {
|
||||||
///
|
///
|
||||||
/// The `prepare` parameter allows access to the database after all
|
/// The `prepare` parameter allows access to the database after all
|
||||||
/// migrations have occurred. This parameter could be replaced by executing
|
/// migrations have occurred. This parameter could be replaced by executing
|
||||||
/// an [`Action`] performing the same operations.
|
|
||||||
///
|
///
|
||||||
/// It is recommended to set a few pragmas before calling this function, for
|
/// It is recommended to set a few pragmas before calling this function, for
|
||||||
/// example:
|
/// example:
|
||||||
/// - `journal_mode` to `"wal"`
|
/// - `journal_mode` to `"wal"`
|
||||||
/// - `foreign_keys` to `true`
|
/// - `foreign_keys` to `true`
|
||||||
/// - `trusted_schema` to `false`
|
/// - `trusted_schema` to `false`
|
||||||
|
/// an [`Action`] performing the same operations.
|
||||||
pub fn new_and_prepare(
|
pub fn new_and_prepare(
|
||||||
mut conn: Connection,
|
mut conn: Connection,
|
||||||
migrations: &[Migration],
|
migrations: &[Migration],
|
||||||
|
|
@ -52,7 +52,11 @@ impl SimpleVault {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute an [`Action`] and return the result.
|
/// Execute an [`Action`] and return the result.
|
||||||
pub fn execute<A: Action>(&mut self, action: A) -> Result<A::Output, A::Error> {
|
pub fn execute<A>(&mut self, action: A) -> rusqlite::Result<A::Result>
|
||||||
|
where
|
||||||
|
A: Action + Send + 'static,
|
||||||
|
A::Result: Send,
|
||||||
|
{
|
||||||
action.run(&mut self.0)
|
action.run(&mut self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
73
src/tokio.rs
73
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, thread};
|
use std::{any::Any, error, fmt, result, thread};
|
||||||
|
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
use tokio::sync::{mpsc, oneshot};
|
use tokio::sync::{mpsc, oneshot};
|
||||||
|
|
@ -12,25 +12,16 @@ 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(
|
fn run(self: Box<Self>, conn: &mut Connection) -> rusqlite::Result<Box<dyn Any + Send>>;
|
||||||
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::Output: Send + 'static,
|
T::Result: Send + 'static,
|
||||||
T::Error: Send + 'static,
|
|
||||||
{
|
{
|
||||||
fn run(
|
fn run(self: Box<Self>, conn: &mut Connection) -> rusqlite::Result<Box<dyn Any + Send>> {
|
||||||
self: Box<Self>,
|
let result = (*self).run(conn)?;
|
||||||
conn: &mut Connection,
|
Ok(Box::new(result))
|
||||||
) -> 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)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,38 +29,46 @@ where
|
||||||
enum Command {
|
enum Command {
|
||||||
Action(
|
Action(
|
||||||
Box<dyn ActionWrapper + Send>,
|
Box<dyn ActionWrapper + Send>,
|
||||||
oneshot::Sender<Result<Box<dyn Any + Send>, Box<dyn Any + Send>>>,
|
oneshot::Sender<rusqlite::Result<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<E> {
|
pub enum Error {
|
||||||
/// 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,
|
||||||
/// An error was returned by the [`Action`].
|
/// A [`rusqlite::Error`] occurred while running the action.
|
||||||
Action(E),
|
Rusqlite(rusqlite::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: fmt::Display> fmt::Display for Error<E> {
|
impl fmt::Display for Error {
|
||||||
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::Action(err) => err.fmt(f),
|
Self::Rusqlite(err) => err.fmt(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: error::Error> error::Error for Error<E> {
|
impl error::Error for Error {
|
||||||
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::Action(err) => err.source(),
|
Self::Rusqlite(err) => Some(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
|
@ -133,34 +132,24 @@ 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::Output, Error<A::Error>>
|
pub async fn execute<A>(&self, action: A) -> Result<A::Result>
|
||||||
where
|
where
|
||||||
A: Action + Send + 'static,
|
A: Action + Send + 'static,
|
||||||
A::Output: 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
|
// The ActionWrapper runs Action::run, which returns Action::Result. It
|
||||||
// Result<Action::Result, Action::Error>. It then wraps the
|
// then wraps this into Any, which we're now trying to downcast again to
|
||||||
// Action::Result and Action::Error into Any, which we're now trying to
|
// Action::Result. This should always work.
|
||||||
// downcast again to Action::Result and Action::Error. This should
|
let result = result.downcast().unwrap();
|
||||||
// always work.
|
|
||||||
match result {
|
Ok(*result)
|
||||||
Ok(result) => {
|
|
||||||
let result = *result.downcast::<A::Output>().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