1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
//! This module contains the core components and traits for building a web
//! server application.
cfg_if::cfg_if! {
if #[cfg(feature = "with-db")] {
use std::path::Path;
use sea_orm::DatabaseConnection;
} else {}
}
use std::sync::Arc;
use async_trait::async_trait;
use axum::Router as AxumRouter;
#[cfg(feature = "channels")]
use crate::controller::channels::AppChannels;
use crate::{
boot::{BootResult, ServeParams, StartMode},
config::{self, Config},
controller::AppRoutes,
environment::Environment,
mailer::EmailSender,
storage::Storage,
task::Tasks,
worker::{Pool, Processor, RedisConnectionManager},
Result,
};
/// Represents the application context for a web server.
///
/// This struct encapsulates various components and configurations required by
/// the web server to operate. It is typically used to store and manage shared
/// resources and settings that are accessible throughout the application's
/// lifetime.
#[derive(Clone)]
#[allow(clippy::module_name_repetitions)]
pub struct AppContext {
/// The environment in which the application is running.
pub environment: Environment,
#[cfg(feature = "with-db")]
/// A database connection used by the application.
pub db: DatabaseConnection,
/// An optional connection pool for Redis, for worker tasks
pub redis: Option<Pool<RedisConnectionManager>>,
/// Configuration settings for the application
pub config: Config,
/// An optional email sender component that can be used to send email.
pub mailer: Option<EmailSender>,
// Ab optional storage instance for the application
pub storage: Option<Arc<Storage>>,
}
/// A trait that defines hooks for customizing and extending the behavior of a
/// web server application.
///
/// Users of the web server application should implement this trait to customize
/// the application's routing, worker connections, task registration, and
/// database actions according to their specific requirements and use cases.
#[async_trait]
pub trait Hooks {
/// Defines the composite app version
#[must_use]
fn app_version() -> String {
"dev".to_string()
}
/// Defines the crate name
///
/// Example
/// ```rust
/// fn app_name() -> &'static str {
/// env!("CARGO_CRATE_NAME")
/// }
/// ```
fn app_name() -> &'static str;
/// Initializes and boots the application based on the specified mode and
/// environment.
///
/// The boot initialization process may vary depending on whether a DB
/// migrator is used or not.
///
/// # Examples
///
/// With DB:
/// ```rust,ignore
/// async fn boot(mode: StartMode, environment: &str) -> Result<BootResult> {
/// create_app::<Self, Migrator>(mode, environment).await
/// }
/// ````
///
/// Without DB:
/// ```rust,ignore
/// async fn boot(mode: StartMode, environment: &str) -> Result<BootResult> {
/// create_app::<Self>(mode, environment).await
/// }
/// ````
///
///
/// # Errors
/// Could not boot the application
async fn boot(mode: StartMode, environment: &Environment) -> Result<BootResult>;
/// Start serving the Axum web application on the specified address and
/// port.
///
/// # Returns
/// A Result indicating success () or an error if the server fails to start.
async fn serve(app: AxumRouter, server_config: ServeParams) -> Result<()> {
let listener = tokio::net::TcpListener::bind(&format!(
"{}:{}",
server_config.binding, server_config.port
))
.await?;
axum::serve(listener, app).await?;
Ok(())
}
/// Override and return `Ok(true)` to provide an alternative logging and
/// tracing stack of your own.
/// When returning `Ok(true)`, Loco will *not* initialize its own logger,
/// so you should set up a complete tracing and logging stack.
///
/// # Errors
/// If fails returns an error
fn init_logger(_config: &config::Config, _env: &Environment) -> Result<bool> {
Ok(false)
}
/// Invoke this function after the Loco routers have been constructed. This
/// function enables you to configure custom Axum logics, such as layers,
/// that are compatible with Axum.
///
/// # Errors
/// Axum router error
async fn after_routes(router: AxumRouter, _ctx: &AppContext) -> Result<AxumRouter> {
Ok(router)
}
/// Provide a list of initializers
/// An initializer can be used to seamlessly add functionality to your app
/// or to initialize some aspects of it.
async fn initializers(_ctx: &AppContext) -> Result<Vec<Box<dyn Initializer>>> {
Ok(vec![])
}
/// Calling the function before run the app
/// You can now code some custom loading of resources or other things before
/// the app runs
async fn before_run(_app_context: &AppContext) -> Result<()> {
Ok(())
}
/// Defines the application's routing configuration.
fn routes(_ctx: &AppContext) -> AppRoutes;
/// Defines the storage configuration for the application
async fn storage(
_config: &config::Config,
_environment: &Environment,
) -> Result<Option<Storage>> {
Ok(None)
}
#[cfg(feature = "channels")]
/// Register channels endpoints to the application routers
fn register_channels(_ctx: &AppContext) -> AppChannels;
/// Connects custom workers to the application using the provided
/// [`Processor`] and [`AppContext`].
fn connect_workers<'a>(p: &'a mut Processor, ctx: &'a AppContext);
/// Registers custom tasks with the provided [`Tasks`] object.
fn register_tasks(tasks: &mut Tasks);
/// Truncates the database as required. Users should implement this
/// function. The truncate controlled from the [`crate::config::Database`]
/// by changing dangerously_truncate to true (default false).
/// Truncate can be useful when you want to truncate the database before any
/// test.
#[cfg(feature = "with-db")]
async fn truncate(db: &DatabaseConnection) -> Result<()>;
/// Seeds the database with initial data.
#[cfg(feature = "with-db")]
async fn seed(db: &DatabaseConnection, path: &Path) -> Result<()>;
}
/// An initializer.
/// Initializers should be kept in `src/initializers/`
#[async_trait]
pub trait Initializer: Sync + Send {
/// The initializer name or identifier
fn name(&self) -> String;
/// Occurs after the app's `before_run`.
/// Use this to for one-time initializations, load caches, perform web
/// hooks, etc.
async fn before_run(&self, _app_context: &AppContext) -> Result<()> {
Ok(())
}
/// Occurs after the app's `after_routes`.
/// Use this to compose additional functionality and wire it into an Axum
/// Router
async fn after_routes(&self, router: AxumRouter, _ctx: &AppContext) -> Result<AxumRouter> {
Ok(router)
}
}