mod build;
use axum::Router;
use std::marker::PhantomData;
use tower_sessions::cookie::time::Duration;
use super::staging::{AdminStaging, CoreStaging, MiddlewareStaging, StaticStaging};
use crate::auth::{PasswordResetConfig, PasswordResetStaging};
use crate::config::RuniqueConfig;
use crate::utils::runique_log::RuniqueLog;
#[cfg(feature = "orm")]
use crate::db::DatabaseConfig;
#[cfg(feature = "orm")]
use sea_orm::DatabaseConnection;
pub mod state {
pub struct No;
pub struct Yes;
pub type Fresh = (No, No, No, No, No, No, No);
mod sealed {
pub trait Sealed {}
impl Sealed for super::No {}
}
macro_rules! once_only {
($(#[$doc:meta])* $name:ident, $message:literal, $label:literal, $note:literal) => {
$(#[$doc])*
#[diagnostic::on_unimplemented(message = $message, label = $label, note = $note)]
pub trait $name: sealed::Sealed {}
impl $name for No {}
};
}
once_only!(
PublicUrlUnset,
"`with_public_url()` has already been called on this builder",
"a second `with_public_url()` would silently replace the first",
"keep a single `.with_public_url(...)`; to tell local from production apart, wrap it in an `if`"
);
once_only!(
RoutesUnset,
"`routes()` has already been called on this builder",
"a second `routes()` would replace every route of the first",
"build a single `Router` (merge or nest the others into it) and pass it once"
);
once_only!(
LogUnset,
"`with_log()` has already been called on this builder",
"a second `with_log()` would start again from an empty log configuration",
"set every category in a single `.with_log(|l| ...)`"
);
once_only!(
PasswordResetUnset,
"`with_password_reset()` has already been called on this builder",
"a second `with_password_reset()` would start again from the default configuration",
"configure everything in a single `.with_password_reset(|pr| ...)`"
);
once_only!(
DatabaseUnset,
"the database has already been set on this builder",
"`with_database()` and `with_database_config()` set the same database: a second call would replace the first",
"pass a single connection or a single configuration"
);
once_only!(
SessionDurationUnset,
"`with_session_duration()` has already been called on this builder",
"a second `with_session_duration()` would silently replace the first",
"keep a single `.with_session_duration(...)`"
);
once_only!(
MailerUnset,
"the mailer has already been set on this builder",
"`with_mailer()` and `with_mailer_from_env()` set the same mailer: the second call would be ignored",
"keep a single mailer call"
);
}
use state::{Fresh, Yes};
#[doc = include_str!("../../../doc-tests/builder/builder_basic.md")]
pub struct RuniqueAppBuilder<S = Fresh> {
pub(super) config: RuniqueConfig,
pub(super) core: CoreStaging,
pub(super) middleware: MiddlewareStaging,
pub(super) statics: StaticStaging,
pub(super) router: Option<Router>,
pub(super) admin: AdminStaging,
pub(super) password_reset: Option<PasswordResetStaging>,
pub(super) state: PhantomData<S>,
}
impl RuniqueAppBuilder<Fresh> {
pub fn new(config: RuniqueConfig) -> Self {
let middleware = MiddlewareStaging::from_config(&config);
Self {
config,
core: CoreStaging::new(),
middleware,
statics: StaticStaging::new(),
router: None,
admin: AdminStaging::new(),
password_reset: None,
state: PhantomData,
}
}
}
impl<S> RuniqueAppBuilder<S> {
fn retype<T>(self) -> RuniqueAppBuilder<T> {
RuniqueAppBuilder {
config: self.config,
core: self.core,
middleware: self.middleware,
statics: self.statics,
router: self.router,
admin: self.admin,
password_reset: self.password_reset,
state: PhantomData,
}
}
}
impl<PU, RT, LG, PR, DB, SD, ML> RuniqueAppBuilder<(PU, RT, LG, PR, DB, SD, ML)> {
pub fn with_public_url(mut self, url: &str) -> RuniqueAppBuilder<(Yes, RT, LG, PR, DB, SD, ML)>
where
PU: state::PublicUrlUnset,
{
let url = url.trim().trim_end_matches('/');
self.config.server.public_url = (!url.is_empty()).then(|| url.to_string());
self.retype()
}
pub fn routes(mut self, router: Router) -> RuniqueAppBuilder<(PU, Yes, LG, PR, DB, SD, ML)>
where
RT: state::RoutesUnset,
{
self.router = Some(router);
self.retype()
}
pub fn with_log(
mut self,
f: impl FnOnce(RuniqueLog) -> RuniqueLog,
) -> RuniqueAppBuilder<(PU, RT, Yes, PR, DB, SD, ML)>
where
LG: state::LogUnset,
{
self.config.log = f(RuniqueLog::new());
self.retype()
}
pub fn with_password_reset(
mut self,
f: impl FnOnce(PasswordResetConfig) -> PasswordResetConfig,
) -> RuniqueAppBuilder<(PU, RT, LG, Yes, DB, SD, ML)>
where
PR: state::PasswordResetUnset,
{
let config = f(PasswordResetConfig::default());
self.password_reset = Some(PasswordResetStaging { config });
self.retype()
}
#[cfg(feature = "orm")]
pub fn with_database(
mut self,
db: DatabaseConnection,
) -> RuniqueAppBuilder<(PU, RT, LG, PR, Yes, SD, ML)>
where
DB: state::DatabaseUnset,
{
self.core = self.core.with_database(db);
self.retype()
}
#[cfg(feature = "orm")]
pub fn with_database_config(
mut self,
config: DatabaseConfig,
) -> RuniqueAppBuilder<(PU, RT, LG, PR, Yes, SD, ML)>
where
DB: state::DatabaseUnset,
{
self.core = self.core.with_database_config(config);
self.retype()
}
pub fn with_session_duration(
mut self,
duration: Duration,
) -> RuniqueAppBuilder<(PU, RT, LG, PR, DB, Yes, ML)>
where
SD: state::SessionDurationUnset,
{
self.middleware = self.middleware.with_session_duration(duration);
self.retype()
}
pub fn with_mailer(
self,
config: crate::utils::mailer::MailerConfig,
) -> RuniqueAppBuilder<(PU, RT, LG, PR, DB, SD, Yes)>
where
ML: state::MailerUnset,
{
crate::utils::mailer::mailer_init(config);
self.retype()
}
pub fn with_mailer_from_env(self) -> RuniqueAppBuilder<(PU, RT, LG, PR, DB, SD, Yes)>
where
ML: state::MailerUnset,
{
crate::utils::mailer::mailer_init_from_env();
self.retype()
}
}
impl<S> RuniqueAppBuilder<S> {
pub fn core(mut self, f: impl FnOnce(CoreStaging) -> CoreStaging) -> Self {
self.core = f(self.core);
self
}
pub fn with_custom_db<T: std::any::Any + Send + Sync + 'static>(mut self, db: T) -> Self {
self.core = self.core.with_extra_db(db);
self
}
pub fn middleware(mut self, f: impl FnOnce(MiddlewareStaging) -> MiddlewareStaging) -> Self {
self.middleware = f(self.middleware);
self
}
pub fn static_files(mut self, f: impl FnOnce(StaticStaging) -> StaticStaging) -> Self {
self.statics = f(self.statics);
self
}
pub fn statics(mut self) -> Self {
self.statics = self.statics.enabled(true);
self
}
pub fn with_admin(mut self, f: impl FnOnce(AdminStaging) -> AdminStaging) -> Self {
self.admin = f(self.admin.enable());
self
}
}