mongod/src/model/mod.rs
2024-07-24 15:44:37 +02:00

278 lines
8.9 KiB
Rust

use mongodb::{
options::{FindOneOptions, FindOptions},
results::{DeleteResult, InsertOneResult},
};
use reference::Referencable;
use serde::de::DeserializeOwned;
use serde_json::{Map, Value};
use valid::Validate;
use crate::{col, collect_results, get_mongo, id_of};
pub mod historic;
pub mod reference;
pub mod update;
pub mod valid;
// todo : use mongodb projection to only get fields you actually use, maybe PartialModel shadow struct?
/// Error type when updating a model
#[derive(Debug)]
pub enum UpdateError {
/// Provided data was no object
NoObject,
/// Database related error
Database(mongodb::error::Error),
/// Validation failed
Validation(String),
}
pub trait Model:
Sized + Referencable + Validate + serde::Serialize + for<'a> serde::Deserialize<'a>
{
type Partial: DeserializeOwned;
/// Insert the `Model` into the database
fn insert(
&self,
) -> impl std::future::Future<Output = Result<InsertOneResult, mongodb::error::Error>> + Send
where
Self: Sync,
{
async {
let db = get_mongo!();
let collection = col!(db, Self::collection_name());
collection
.insert_one(mongodb::bson::to_document(self).unwrap(), None)
.await
}
}
/// Insert or update an existing `Model`
fn insert_overwrite(
&self,
) -> impl std::future::Future<Output = Result<(), mongodb::error::Error>> + Send
where
Self: Sync,
{
async {
if self.insert().await.is_err() {
let db = get_mongo!();
let collection = col!(db, Self::collection_name());
collection
.update_one(
id_of!(self.id()),
mongodb::bson::doc! {"$set": mongodb::bson::to_document(self).unwrap() },
None,
)
.await?;
}
Ok(())
}
}
/// Remove a `Model` from the database.
#[must_use]
fn remove(
id: &str,
) -> impl std::future::Future<Output = Result<DeleteResult, mongodb::error::Error>> + Send {
async move {
let db = get_mongo!();
let collection = col!(db, Self::collection_name());
collection.delete_one(id_of!(id), None).await
}
}
/// Remove a `Model` from the database.
///
/// This is a convenience function to let you call `remove()` on a `Model` you have at hand.
fn delete(
&self,
) -> impl std::future::Future<Output = Result<DeleteResult, mongodb::error::Error>> + Send
where
Self: Sync,
{
async { Self::remove(self.id()).await }
}
/// Get a `Model` by id from the database
#[must_use]
fn get(id: &str) -> impl std::future::Future<Output = Option<Self>> {
async move {
let db = get_mongo!();
let collection = col!(db, Self::collection_name());
let doc = collection.find_one(id_of!(id), None).await.ok()??;
mongodb::bson::from_document(doc).ok()
}
}
/// Get a partial `Model` by id from the database with only some fields retrieved.
#[must_use]
fn get_partial(
id: &str,
mut part: serde_json::Value,
) -> impl std::future::Future<Output = Option<Self::Partial>> {
async move {
let db = get_mongo!();
let collection = col!(db, Self::collection_name());
part.as_object_mut()?.insert("_id".into(), 1.into());
let doc = collection
.find_one(
id_of!(id),
Some(
FindOneOptions::builder()
.projection(Some(mongodb::bson::to_document(&part).unwrap()))
.build(),
),
)
.await
.ok()??;
mongodb::bson::from_document(doc).ok()
}
}
/// Get a `Model` by using a filter from the database
#[must_use]
fn find_one_partial(
filter: mongodb::bson::Document,
mut part: serde_json::Value,
) -> impl std::future::Future<Output = Option<Self::Partial>> {
async move {
let db = get_mongo!();
let collection = col!(db, Self::collection_name());
part.as_object_mut()?.insert("_id".into(), 1.into());
let doc = collection
.find_one(
filter,
Some(
FindOneOptions::builder()
.projection(Some(mongodb::bson::to_document(&part).unwrap()))
.build(),
),
)
.await
.ok()??;
mongodb::bson::from_document(doc).ok()
}
}
/// Get a partial `Model` by using a filter from the database
#[must_use]
fn find_one(
filter: mongodb::bson::Document,
) -> impl std::future::Future<Output = Option<Self>> {
async move {
let db = get_mongo!();
let collection = col!(db, Self::collection_name());
let doc = collection.find_one(filter, None).await.ok()??;
mongodb::bson::from_document(doc).ok()
}
}
/// Get multiple `Model`s by using a filter from the database. Pass a `limit` parameter to limit the amount of `Model`s returned.
#[must_use]
fn find(
filter: mongodb::bson::Document,
limit: Option<i64>,
) -> impl std::future::Future<Output = Option<Vec<Self>>> {
async move {
let db = get_mongo!();
let collection = col!(db, Self::collection_name());
let mut results = collection
.find(
filter,
limit.map(|x| FindOptions::builder().limit(x).build()),
)
.await
.ok()?;
let docs = collect_results!(results);
docs.into_iter()
.map(|x| mongodb::bson::from_document(x).unwrap())
.collect()
}
}
/// Get multiple partial `Model`s by using a filter from the database. Pass a `limit` parameter to limit the amount of `Model`s returned.
#[must_use]
fn find_partial(
filter: mongodb::bson::Document,
mut part: serde_json::Value,
limit: Option<i64>,
) -> impl std::future::Future<Output = Option<Vec<Self>>> {
async move {
let db = get_mongo!();
let collection = col!(db, Self::collection_name());
part.as_object_mut()?.insert("_id".into(), 1.into());
let mut results = collection
.find(
filter,
Some(
FindOptions::builder()
.projection(Some(mongodb::bson::to_document(&part).unwrap()))
.limit(limit)
.build(),
),
)
.await
.ok()?;
let docs = collect_results!(results);
docs.into_iter()
.map(|x| mongodb::bson::from_document(x).unwrap())
.collect()
}
}
/// Update values of `Model` into database
fn update(
&mut self,
data: &serde_json::Value,
) -> impl std::future::Future<Output = Result<(), UpdateError>> {
async {
// get db collection
let db = get_mongo!();
let collection = col!(db, Self::collection_name());
let mut update = mongodb::bson::Document::new();
if let Some(obj) = data.as_object() {
// run model specific update
self.update_values(obj, &mut update).await;
// validate and update
if let Err(msg) = self.validate().await {
return Err(UpdateError::Validation(msg));
}
collection
.update_one(
id_of!(self.id()),
mongodb::bson::doc! {"$set": update },
None,
)
.await
.map_err(UpdateError::Database)?;
return Ok(());
}
Err(UpdateError::NoObject)
}
}
/// Update the `Model` based on the provided JSON Object.
///
/// This function should be implemented for a `Model`. It should update all keys from the JSON Object.
/// For every updated value in the JSON Object the `Model`s fields should be updated and the value should be put in the `update` `Document`
///
/// To avoid making mistakes in the Update logic use the macros from `update.rs`
fn update_values(
&mut self,
obj: &Map<String, Value>,
update: &mut mongodb::bson::Document,
) -> impl std::future::Future<Output = ()> + Send;
}