#![doc = include_str!("../docs/getting-started.md")]
#![warn(clippy::all, clippy::pedantic, clippy::nursery)]
#![allow(clippy::inconsistent_struct_constructor)]
#[doc(hidden)]
pub use async_trait::async_trait;
use connection::ConnectError;
#[doc(hidden)]
pub use inflector::Inflector;
#[doc(hidden)]
pub use rbs;
#[doc(hidden)]
pub use serde;
#[doc(hidden)]
#[cfg(feature = "json")]
pub use serde_json;
use query::{Builder, EagerLoad};
use serde::{de::DeserializeOwned, Serialize};
use std::{
collections::HashMap,
fmt::{Debug, Display},
future::Future,
};
mod connection;
pub mod migrations;
pub mod query;
pub mod relationships;
pub mod types;
pub mod value;
#[cfg(any(feature = "mysql", feature = "postgres"))]
pub use connection::setup;
pub use ensemble_derive::Model;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Connection(#[from] ConnectError),
#[cfg(feature = "validator")]
#[error(transparent)]
Validation(#[from] validator::ValidationErrors),
#[error("{0}")]
Database(String),
#[error("The {0} field is required.")]
Required(&'static str),
#[error("Failed to serialize model.")]
Serialization(#[from] rbs::value::ext::Error),
#[error("The model could not be found.")]
NotFound,
#[error("The unique constraint was violated.")]
UniqueViolation,
#[error("The query is invalid.")]
InvalidQuery,
}
pub trait Model: DeserializeOwned + Serialize + Sized + Send + Sync + Debug + Default {
type PrimaryKey: Display
+ DeserializeOwned
+ Serialize
+ PartialEq
+ Default
+ Clone
+ Send
+ Sync;
const NAME: &'static str;
const TABLE_NAME: &'static str;
const PRIMARY_KEY: &'static str;
fn primary_key(&self) -> &Self::PrimaryKey;
#[must_use]
fn all() -> impl Future<Output = Result<Vec<Self>, Error>> + Send {
async { Self::query().get().await }
}
fn find(key: Self::PrimaryKey) -> impl Future<Output = Result<Self, Error>> + Send;
fn create(self) -> impl Future<Output = Result<Self, Error>> + Send;
fn save(&mut self) -> impl Future<Output = Result<(), Error>> + Send;
#[allow(unused_mut)]
fn delete(mut self) -> impl Future<Output = Result<(), Error>> + Send {
async move {
let rows_affected = Self::query()
.r#where(
Self::PRIMARY_KEY,
"=",
value::for_db(self.primary_key()).unwrap(),
)
.delete()
.await?;
if rows_affected != 1 {
return Err(Error::UniqueViolation);
}
Ok(())
}
}
fn fresh(&self) -> impl Future<Output = Result<Self, Error>> + Send;
#[must_use]
fn query() -> Builder {
Builder::new(Self::TABLE_NAME.to_string())
}
fn with<T: Into<EagerLoad>>(eager_load: T) -> Builder {
Self::query().with(eager_load)
}
fn load<T: Into<EagerLoad> + Send>(
&mut self,
relation: T,
) -> impl Future<Output = Result<(), Error>> + Send {
async move {
for relation in relation.into().list() {
let rows = self.eager_load(&relation, &[&self]).get_rows().await?;
self.fill_relation(&relation, &rows)?;
}
Ok(())
}
}
fn increment(
&mut self,
column: &str,
amount: u64,
) -> impl Future<Output = Result<(), Error>> + Send {
async move {
let rows_affected = Self::query()
.r#where(
Self::PRIMARY_KEY,
"=",
value::for_db(self.primary_key()).unwrap(),
)
.increment(column, amount)
.await?;
if rows_affected != 1 {
return Err(Error::UniqueViolation);
}
Ok(())
}
}
#[cfg(feature = "json")]
fn json(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap()
}
#[doc(hidden)]
fn eager_load(&self, relation: &str, related: &[&Self]) -> Builder;
#[doc(hidden)]
fn fill_relation(
&mut self,
relation: &str,
related: &[HashMap<String, rbs::Value>],
) -> Result<(), Error>;
}
pub trait Collection {
fn load<T>(&mut self, relation: T) -> impl Future<Output = Result<(), Error>> + Send
where
T: Into<EagerLoad> + Send + Sync + Clone;
#[cfg(feature = "json")]
fn json(&self) -> serde_json::Value;
}
impl<T: Model> Collection for &mut Vec<T> {
async fn load<U>(&mut self, relation: U) -> Result<(), Error>
where
U: Into<EagerLoad> + Send + Sync + Clone,
{
for model in self.iter_mut() {
model.load(relation.clone()).await?;
}
Ok(())
}
#[cfg(feature = "json")]
fn json(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap()
}
}