Compare commits

..

No commits in common. "master" and "v0.1.0" have entirely different histories.

6 changed files with 42 additions and 422 deletions

View file

@ -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/).
Procedure when bumping the version number:
1. Update dependencies in a separate commit
2. Set version number in `Cargo.toml`
3. Add new section in this changelog
@ -14,45 +13,6 @@ Procedure when bumping the version number:
## 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
Initial release

View file

@ -1,13 +1,11 @@
[package]
name = "vault"
version = "0.5.0"
version = "0.1.0"
edition = "2021"
[features]
serde = ["dep:serde"]
tokio = ["dep:tokio"]
[dependencies]
rusqlite = "0.33.0"
serde = { version = "1.0.209", optional = true }
tokio = { version = "1.40.0", features = ["sync"], optional = true }
rusqlite = "0.28.0"
tokio = { version = "1.25.0", features = ["sync"], optional = true }

View file

@ -9,17 +9,12 @@
// Clippy lints
#![warn(clippy::use_self)]
#[cfg(feature = "serde")]
pub mod serde;
pub mod simple;
#[cfg(feature = "tokio")]
pub mod tokio;
use rusqlite::{Connection, Transaction};
#[cfg(feature = "serde")]
pub use self::serde::*;
/// An action that can be performed on a [`Connection`].
///
/// 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
/// return the result. The way in which this occurs depends on the vault.
pub trait Action {
type Output;
type Error;
fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error>;
type Result;
fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result>;
}
/// A single database migration.

View file

@ -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())
}

View file

@ -34,13 +34,13 @@ impl SimpleVault {
///
/// The `prepare` parameter allows access to the database after all
/// 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
/// example:
/// - `journal_mode` to `"wal"`
/// - `foreign_keys` to `true`
/// - `trusted_schema` to `false`
/// an [`Action`] performing the same operations.
pub fn new_and_prepare(
mut conn: Connection,
migrations: &[Migration],
@ -52,7 +52,11 @@ impl SimpleVault {
}
/// 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)
}
}

View file

@ -1,6 +1,6 @@
//! 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 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.
trait ActionWrapper {
fn run(
self: Box<Self>,
conn: &mut Connection,
) -> Result<Box<dyn Any + Send>, Box<dyn Any + Send>>;
fn run(self: Box<Self>, conn: &mut Connection) -> rusqlite::Result<Box<dyn Any + Send>>;
}
impl<T: Action> ActionWrapper for T
where
T::Output: Send + 'static,
T::Error: Send + 'static,
T::Result: Send + 'static,
{
fn run(
self: Box<Self>,
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)),
}
fn run(self: Box<Self>, conn: &mut Connection) -> rusqlite::Result<Box<dyn Any + Send>> {
let result = (*self).run(conn)?;
Ok(Box::new(result))
}
}
@ -38,38 +29,46 @@ where
enum Command {
Action(
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<()>),
}
/// Error that can occur during execution of an [`Action`].
#[derive(Debug)]
pub enum Error<E> {
pub enum Error {
/// The vault's thread has been stopped and its sqlite connection closed.
Stopped,
/// An error was returned by the [`Action`].
Action(E),
/// A [`rusqlite::Error`] occurred while running the action.
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 {
match self {
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)> {
match self {
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>) {
while let Some(command) = rx.blocking_recv() {
match command {
@ -133,34 +132,24 @@ impl TokioVault {
}
/// 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
A: Action + Send + 'static,
A::Output: Send,
A::Error: Send,
A::Result: Send,
{
let (tx, rx) = oneshot::channel();
self.tx
.send(Command::Action(Box::new(action), tx))
.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
// Result<Action::Result, Action::Error>. It then wraps the
// Action::Result and Action::Error into Any, which we're now trying to
// downcast again to Action::Result and Action::Error. This should
// always work.
match 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))
}
}
// The ActionWrapper runs Action::run, which returns Action::Result. It
// then wraps this into Any, which we're now trying to downcast again to
// Action::Result. This should always work.
let result = result.downcast().unwrap();
Ok(*result)
}
/// Stop the vault's thread and close its sqlite connection.