mirror of
https://github.com/Drop-OSS/dropbreak.git
synced 2026-07-18 18:04:32 -04:00
Add and document some more changes
This commit is contained in:
+1
-1
@@ -22,7 +22,7 @@ type DB = FileDatabase<Config, Yaml>;
|
||||
|
||||
lazy_static! {
|
||||
static ref CONFIG: DB = {
|
||||
let db = FileDatabase::from_path(Config::default(), Yaml, "/tmp/config.yml")
|
||||
let db = FileDatabase::from_path("/tmp/config.yml", Config::default())
|
||||
.expect("Create database from path");
|
||||
db.reload().expect("Config to load");
|
||||
db
|
||||
|
||||
+1
-2
@@ -18,8 +18,7 @@ struct Person {
|
||||
fn main() {
|
||||
use std::collections::HashMap;
|
||||
|
||||
let db = FileDatabase::<HashMap<String, Person>, Ron>::from_path(HashMap::new(),
|
||||
Ron, "test.ron").unwrap();
|
||||
let db = FileDatabase::<HashMap<String, Person>, Ron>::from_path("test.ron", HashMap::new()).unwrap();
|
||||
|
||||
println!("Writing to Database");
|
||||
db.write(|db| {
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
/* This file is a complete work in progress! And is meant to illustrate a possible way of
|
||||
* migrating databases with Rustbreak.
|
||||
*
|
||||
* Currently the major challenge is to statically define the upgrade process.
|
||||
*
|
||||
* This is currently done by using the `upgrading_macro`.
|
||||
*
|
||||
* The idea is to read the 'version' tag in the file, load it into a simple tag struct and
|
||||
* read the version from there. This version is then used to dynamically get the corresponding
|
||||
* struct type. This then gets converted to the latest version.
|
||||
*/
|
||||
|
||||
|
||||
extern crate rustbreak;
|
||||
#[macro_use] extern crate serde_derive;
|
||||
|
||||
use rustbreak::FileDatabase;
|
||||
use rustbreak::deser::Ron;
|
||||
|
||||
mod data {
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
|
||||
pub enum Version {
|
||||
First, Second, Third,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Versioning {
|
||||
pub version: Version
|
||||
}
|
||||
|
||||
pub mod v1 {
|
||||
use data::Version;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Players {
|
||||
version: Version,
|
||||
pub players: HashMap<i32, String>,
|
||||
}
|
||||
|
||||
impl Players {
|
||||
pub fn new() -> Players {
|
||||
Players {
|
||||
version: Version::First,
|
||||
players: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod v2 {
|
||||
use data::Version;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Player {
|
||||
pub name: String,
|
||||
pub level: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Players {
|
||||
version: Version,
|
||||
pub players: HashMap<i32, Player>,
|
||||
}
|
||||
|
||||
impl Players {
|
||||
pub fn new() -> Players {
|
||||
Players {
|
||||
version: Version::Second,
|
||||
players: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod v3 {
|
||||
use data::Version;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Player {
|
||||
pub name: String,
|
||||
pub score: i64
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Players {
|
||||
version: Version,
|
||||
pub players: HashMap<i32, Player>,
|
||||
}
|
||||
|
||||
impl Players {
|
||||
pub fn new() -> Players {
|
||||
Players {
|
||||
version: Version::Third,
|
||||
players: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_v1_v2(input: v1::Players) -> v2::Players {
|
||||
let mut new = v2::Players::new();
|
||||
for (key, player) in input.players {
|
||||
new.players.insert(key,
|
||||
v2::Player {
|
||||
name: player,
|
||||
level: 1
|
||||
});
|
||||
}
|
||||
|
||||
new
|
||||
}
|
||||
|
||||
pub fn convert_v2_v3(input: v2::Players) -> v3::Players {
|
||||
let mut new = v3::Players::new();
|
||||
for (key, player) in input.players {
|
||||
new.players.insert(key,
|
||||
v3::Player {
|
||||
name: player.name,
|
||||
score: 0
|
||||
});
|
||||
}
|
||||
|
||||
new
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! migration_rules {
|
||||
( $name:ident -> $res:ty { $( $t:ty => $conv:path ),* $(,)* } ) => {
|
||||
fn $name<T: 'static>(input: T) -> Option<$res> {
|
||||
fn inner(mut input: Box<std::any::Any>) -> Option<$res> {
|
||||
println!("We have: {:#?}", input);
|
||||
$(
|
||||
{
|
||||
let mut maybe_in = None;
|
||||
if let Some(out) = input.downcast_ref::<$t>() {
|
||||
maybe_in = Some(Box::new($conv(out.clone())));
|
||||
}
|
||||
|
||||
if let Some(inp) = maybe_in {
|
||||
input = inp;
|
||||
}
|
||||
}
|
||||
)*
|
||||
|
||||
if let Ok(out) = input.downcast::<$res>() {
|
||||
return Some(*out);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
inner(Box::new(input))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
migration_rules! {
|
||||
update_database -> data::v3::Players {
|
||||
data::v1::Players => data::convert_v1_v2,
|
||||
data::v2::Players => data::convert_v2_v3,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_version() -> data::Version {
|
||||
let db = FileDatabase::<data::Versioning, Ron>::from_path("migration.ron", data::Versioning { version: data::Version::First }).unwrap();
|
||||
db.reload().unwrap();
|
||||
db.read(|ver| ver.version).unwrap()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
use data::v3::Players;
|
||||
|
||||
let version = get_version();
|
||||
|
||||
|
||||
// Let's check if there are any updates
|
||||
let updated_data = update_database(database.get_data(false).unwrap()).unwrap();
|
||||
let database = FileDatabase::<Players, Ron>::from_path("migration.ron", updated_data).unwrap();
|
||||
|
||||
println!("{:#?}", database);
|
||||
}
|
||||
@@ -18,8 +18,7 @@ struct Person {
|
||||
fn main() {
|
||||
use std::collections::HashMap;
|
||||
|
||||
let db = FileDatabase::<HashMap<String, Person>, Ron>::from_path(HashMap::new(),
|
||||
Ron, "test.ron").unwrap();
|
||||
let db = FileDatabase::<HashMap<String, Person>, Ron>::from_path("test.ron", HashMap::new()).unwrap();
|
||||
|
||||
println!("Writing to Database");
|
||||
db.write(|db| {
|
||||
|
||||
@@ -17,6 +17,7 @@ pub trait Backend {
|
||||
}
|
||||
|
||||
/// A backend using a file
|
||||
#[derive(Debug)]
|
||||
pub struct FileBackend(::std::fs::File);
|
||||
|
||||
impl Backend for FileBackend {
|
||||
@@ -63,6 +64,7 @@ impl FileBackend {
|
||||
/// An in memory backend
|
||||
///
|
||||
/// It is backed by a `Vec<u8>`
|
||||
#[derive(Debug)]
|
||||
pub struct MemoryBackend(Vec<u8>);
|
||||
|
||||
impl MemoryBackend {
|
||||
|
||||
+4
-4
@@ -20,7 +20,7 @@ pub use self::yaml::Yaml;
|
||||
pub use self::bincode::Bincode;
|
||||
|
||||
/// A trait to bundle serializer and deserializer
|
||||
pub trait DeSerializer<T: Serialize + DeserializeOwned> : ::std::fmt::Debug + Send + Sync {
|
||||
pub trait DeSerializer<T: Serialize + DeserializeOwned> : ::std::default::Default + Send + Sync + Clone {
|
||||
/// Serializes a given value to a String
|
||||
fn serialize(&self, val: &T) -> error::Result<String>;
|
||||
/// Deserializes a String to a value
|
||||
@@ -28,7 +28,7 @@ pub trait DeSerializer<T: Serialize + DeserializeOwned> : ::std::fmt::Debug + Se
|
||||
}
|
||||
|
||||
/// The Struct that allows you to use `ron` the Rusty Object Notation
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Ron;
|
||||
|
||||
impl<T: Serialize + DeserializeOwned> DeSerializer<T> for Ron {
|
||||
@@ -52,7 +52,7 @@ mod yaml {
|
||||
use deser::DeSerializer;
|
||||
|
||||
/// The struct that allows you to use yaml
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Yaml;
|
||||
|
||||
impl<T: Serialize + DeserializeOwned> DeSerializer<T> for Yaml {
|
||||
@@ -78,7 +78,7 @@ mod bincode {
|
||||
use deser::DeSerializer;
|
||||
|
||||
/// The struct that allows you to use bincode
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Bincode;
|
||||
|
||||
impl<T: Serialize + DeserializeOwned> DeSerializer<T> for Bincode {
|
||||
|
||||
+144
-34
@@ -21,7 +21,7 @@
|
||||
|
||||
//! # Rustbreak
|
||||
//!
|
||||
//! Rustbreak is a [Daybreak][daybreak] inspiried single file Database.
|
||||
//! Rustbreak is a [Daybreak][daybreak] inspired single file Database.
|
||||
//!
|
||||
//! You will find an overview here in the docs, but to give you a more complete tale of how this is
|
||||
//! used please check the [examples][examples].
|
||||
@@ -46,10 +46,14 @@
|
||||
//! ## Quickstart
|
||||
//!
|
||||
//! ```rust
|
||||
//! # extern crate failure;
|
||||
//! # extern crate rustbreak;
|
||||
//! # use std::collections::HashMap;
|
||||
//! use rustbreak::{MemoryDatabase, deser::Ron};
|
||||
//!
|
||||
//! let db = MemoryDatabase::<HashMap<String, String>, Ron>::memory(HashMap::new(), Ron);
|
||||
//! # fn main() {
|
||||
//! # let func = || -> Result<(), failure::Error> {
|
||||
//! let db = MemoryDatabase::<HashMap<String, String>, Ron>::memory(HashMap::new())?;
|
||||
//!
|
||||
//! println!("Writing to Database");
|
||||
//! db.write(|db| {
|
||||
@@ -62,6 +66,9 @@
|
||||
//! // The above line will not compile since we are only reading
|
||||
//! println!("Hello: {:?}", db.get("hello"));
|
||||
//! });
|
||||
//! # return Ok(()); };
|
||||
//! # func().unwrap();
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! [daybreak]:https://propublica.github.io/daybreak
|
||||
@@ -112,8 +119,8 @@ use backend::{Backend, MemoryBackend, FileBackend};
|
||||
pub struct Database<Data, Back, DeSer>
|
||||
where
|
||||
Data: Serialize + DeserializeOwned + Debug + Clone + Send,
|
||||
Back: Backend,
|
||||
DeSer: DeSerializer<Data> + Send + Sync
|
||||
Back: Backend + Debug,
|
||||
DeSer: DeSerializer<Data> + Debug + Send + Sync + Clone
|
||||
{
|
||||
data: RwLock<Data>,
|
||||
backend: Mutex<Back>,
|
||||
@@ -123,8 +130,8 @@ pub struct Database<Data, Back, DeSer>
|
||||
impl<Data, Back, DeSer> Database<Data, Back, DeSer>
|
||||
where
|
||||
Data: Serialize + DeserializeOwned + Debug + Clone + Send,
|
||||
Back: Backend,
|
||||
DeSer: DeSerializer<Data> + Send + Sync
|
||||
Back: Backend + Debug,
|
||||
DeSer: DeSerializer<Data> + Debug + Send + Sync + Clone
|
||||
{
|
||||
/// Write lock the database and get write access to the `Data` container
|
||||
pub fn write<T>(&self, task: T) -> error::Result<()>
|
||||
@@ -143,17 +150,21 @@ impl<Data, Back, DeSer> Database<Data, Back, DeSer>
|
||||
Ok(task(&mut lock))
|
||||
}
|
||||
|
||||
/// Reload the Data from the backend
|
||||
pub fn reload(&self) -> error::Result<()> {
|
||||
fn load(backend: &mut Back, deser: &DeSer) -> error::Result<Data> {
|
||||
use failure::ResultExt;
|
||||
|
||||
let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::PoisonError)?;
|
||||
let mut data = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?;
|
||||
|
||||
let mut new_data = self.deser.deserialize(&backend.get_data()?[..])
|
||||
let new_data = deser.deserialize(&backend.get_data()?[..])
|
||||
.context(error::RustbreakErrorKind::DeserializationError)?;
|
||||
|
||||
::std::mem::swap(&mut *data, &mut new_data);
|
||||
Ok(new_data)
|
||||
}
|
||||
|
||||
/// Reload the Data from the backend
|
||||
pub fn reload(&self) -> error::Result<()> {
|
||||
|
||||
let mut data = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?;
|
||||
let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::PoisonError)?;
|
||||
|
||||
*data = Self::load(&mut backend, &self.deser)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -171,6 +182,39 @@ impl<Data, Back, DeSer> Database<Data, Back, DeSer>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get a clone of the data as it is in memory right now
|
||||
///
|
||||
/// To make sure you have the latest data, call this method with `reload` true
|
||||
pub fn get_data(&self, reload: bool) -> error::Result<Data> {
|
||||
let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::PoisonError)?;
|
||||
let mut data = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?;
|
||||
if reload {
|
||||
*data = Self::load(&mut backend, &self.deser)?;
|
||||
drop(backend);
|
||||
}
|
||||
Ok(data.clone())
|
||||
}
|
||||
|
||||
/// Puts the data as is into memory
|
||||
///
|
||||
/// To sync the data afterwards, call with `sync` true.
|
||||
pub fn put_data(&self, new_data: Data, sync: bool) -> error::Result<()> {
|
||||
let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::PoisonError)?;
|
||||
let mut data = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?;
|
||||
if sync {
|
||||
// TODO: Spin this into its own method
|
||||
use failure::ResultExt;
|
||||
|
||||
let ser = self.deser.serialize(&*data)
|
||||
.context(error::RustbreakErrorKind::SerializationError)?;
|
||||
|
||||
backend.put_data(ser.as_bytes())?;
|
||||
drop(backend);
|
||||
}
|
||||
*data = new_data;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a database from its constituents
|
||||
pub fn from_parts(data: Data, backend: Back, deser: DeSer) -> Database<Data, Back, DeSer> {
|
||||
Database {
|
||||
@@ -186,6 +230,58 @@ impl<Data, Back, DeSer> Database<Data, Back, DeSer>
|
||||
self.backend.into_inner().map_err(|_| error::RustbreakErrorKind::PoisonError)?,
|
||||
self.deser))
|
||||
}
|
||||
|
||||
/// Tries to clone the Data in the Database.
|
||||
///
|
||||
/// This method returns a `MemoryDatabase` which has an empty vector as a
|
||||
/// backend initially. This means that the user is responsible for assigning a new backend
|
||||
/// if an alternative is wanted.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate serde_derive;
|
||||
/// # extern crate rustbreak;
|
||||
/// # extern crate serde;
|
||||
/// # extern crate tempfile;
|
||||
/// # extern crate failure;
|
||||
///
|
||||
/// use rustbreak::{FileDatabase, deser::Ron};
|
||||
///
|
||||
/// #[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
/// struct Data {
|
||||
/// level: u32,
|
||||
/// }
|
||||
///
|
||||
/// # fn main() {
|
||||
/// # let func = || -> Result<(), failure::Error> {
|
||||
/// # let file = tempfile::tempfile()?;
|
||||
/// let db = FileDatabase::<Data, Ron>::from_file(file, Data { level: 0 })?;
|
||||
///
|
||||
/// db.write(|db| {
|
||||
/// db.level = 42;
|
||||
/// })?;
|
||||
///
|
||||
/// db.sync()?;
|
||||
///
|
||||
/// let other_db = db.try_clone()?;
|
||||
///
|
||||
/// let value = other_db.read(|db| db.level)?;
|
||||
/// assert_eq!(42, value);
|
||||
/// # return Ok(());
|
||||
/// # };
|
||||
/// # func().unwrap();
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn try_clone(&self) -> error::Result<MemoryDatabase<Data, DeSer>> {
|
||||
let lock = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?;
|
||||
|
||||
Ok(Database {
|
||||
data: RwLock::new(lock.clone()),
|
||||
backend: Mutex::new(MemoryBackend::new()),
|
||||
deser: self.deser.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A database backed by a file
|
||||
@@ -194,19 +290,31 @@ pub type FileDatabase<D, DS> = Database<D, FileBackend, DS>;
|
||||
impl<Data, DeSer> Database<Data, FileBackend, DeSer>
|
||||
where
|
||||
Data: Serialize + DeserializeOwned + Debug + Clone + Send,
|
||||
DeSer: DeSerializer<Data> + Send + Sync
|
||||
DeSer: DeSerializer<Data> + Debug + Send + Sync + Clone
|
||||
{
|
||||
/// Create new FileDatabase from Path
|
||||
pub fn from_path<S>(data: Data, deser: DeSer, path: S)
|
||||
pub fn from_path<S>(path: S, data: Data)
|
||||
-> error::Result<FileDatabase<Data, DeSer>>
|
||||
where S: AsRef<std::path::Path>
|
||||
{
|
||||
let b = FileBackend::open(path)?;
|
||||
let backend = FileBackend::open(path)?;
|
||||
|
||||
Ok(Database {
|
||||
data: RwLock::new(data),
|
||||
backend: Mutex::new(b),
|
||||
deser: deser,
|
||||
backend: Mutex::new(backend),
|
||||
deser: DeSer::default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Create new FileDatabase from a file
|
||||
pub fn from_file(file: ::std::fs::File, data: Data) -> error::Result<FileDatabase<Data, DeSer>>
|
||||
{
|
||||
let backend = FileBackend::from_file(file);
|
||||
|
||||
Ok(Database {
|
||||
data: RwLock::new(data),
|
||||
backend: Mutex::new(backend),
|
||||
deser: DeSer::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -217,27 +325,29 @@ pub type MemoryDatabase<D, DS> = Database<D, MemoryBackend, DS>;
|
||||
impl<Data, DeSer> Database<Data, MemoryBackend, DeSer>
|
||||
where
|
||||
Data: Serialize + DeserializeOwned + Debug + Clone + Send,
|
||||
DeSer: DeSerializer<Data> + Send + Sync
|
||||
DeSer: DeSerializer<Data> + Debug + Send + Sync + Clone
|
||||
{
|
||||
/// Create new FileDatabase from Path
|
||||
pub fn memory(data: Data, deser: DeSer) -> MemoryDatabase<Data, DeSer> {
|
||||
Database {
|
||||
pub fn memory(data: Data) -> error::Result<MemoryDatabase<Data, DeSer>> {
|
||||
let backend = MemoryBackend::new();
|
||||
|
||||
Ok(Database {
|
||||
data: RwLock::new(data),
|
||||
backend: Mutex::new(MemoryBackend::new()),
|
||||
deser: deser,
|
||||
}
|
||||
backend: Mutex::new(backend),
|
||||
deser: DeSer::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<Data, Back, DeSer> Database<Data, Back, DeSer>
|
||||
where
|
||||
Data: Serialize + DeserializeOwned + Debug + Clone + Send,
|
||||
Back: Backend,
|
||||
DeSer: DeSerializer<Data> + Send + Sync
|
||||
Back: Backend + Debug,
|
||||
DeSer: DeSerializer<Data> + Debug + Send + Sync + Clone
|
||||
{
|
||||
/// Exchanges the DeSerialization strategy with the given one
|
||||
pub fn with_deser<T>(self, deser: T) -> Database<Data, Back, T>
|
||||
where T: DeSerializer<Data> + Send + Sync
|
||||
where T: DeSerializer<Data> + Debug + Send + Sync
|
||||
{
|
||||
Database {
|
||||
backend: self.backend,
|
||||
@@ -250,12 +360,12 @@ impl<Data, Back, DeSer> Database<Data, Back, DeSer>
|
||||
impl<Data, Back, DeSer> Database<Data, Back, DeSer>
|
||||
where
|
||||
Data: Serialize + DeserializeOwned + Debug + Clone + Send,
|
||||
Back: Backend,
|
||||
DeSer: DeSerializer<Data> + Send + Sync
|
||||
Back: Backend + Debug,
|
||||
DeSer: DeSerializer<Data> + Debug + Send + Sync + Clone
|
||||
{
|
||||
/// Exchanges the Backend with the given one
|
||||
pub fn with_backend<T>(self, backend: T) -> Database<Data, T, DeSer>
|
||||
where T: Backend
|
||||
where T: Backend + Debug
|
||||
{
|
||||
Database {
|
||||
backend: Mutex::new(backend),
|
||||
@@ -268,8 +378,8 @@ impl<Data, Back, DeSer> Database<Data, Back, DeSer>
|
||||
impl<Data, Back, DeSer> Database<Data, Back, DeSer>
|
||||
where
|
||||
Data: Serialize + DeserializeOwned + Debug + Clone + Send,
|
||||
Back: Backend,
|
||||
DeSer: DeSerializer<Data> + Send + Sync
|
||||
Back: Backend + Debug,
|
||||
DeSer: DeSerializer<Data> + Debug + Send + Sync + Clone
|
||||
{
|
||||
/// Converts from one data type to another
|
||||
///
|
||||
@@ -279,7 +389,7 @@ impl<Data, Back, DeSer> Database<Data, Back, DeSer>
|
||||
where
|
||||
OutputData: Serialize + DeserializeOwned + Debug + Clone + Send,
|
||||
C: FnOnce(Data) -> OutputData,
|
||||
DeSer: DeSerializer<OutputData>,
|
||||
DeSer: DeSerializer<OutputData> + Debug + Send + Sync,
|
||||
{
|
||||
let (data, backend, deser) = self.into_inner()?;
|
||||
Ok(Database {
|
||||
|
||||
Reference in New Issue
Block a user