Skip to main content

ferro_rs/database/
mod.rs

1//! Database module for Ferro framework
2//!
3//! Provides a SeaORM-based ORM with Laravel-like API.
4//!
5//! # Quick Start
6//!
7//! ```rust,ignore
8//! use ferro_rs::{Config, DB, DatabaseConfig};
9//!
10//! // 1. Register database config (in config/mod.rs)
11//! Config::register(DatabaseConfig::from_env());
12//!
13//! // 2. Initialize connection (in bootstrap.rs)
14//! DB::init().await.expect("Failed to connect to database");
15//!
16//! // 3. Use in controllers
17//! let conn = DB::connection()?;
18//! let users = User::find().all(conn.inner()).await?;
19//! ```
20//!
21//! # Configuration
22//!
23//! Set these environment variables:
24//!
25//! ```env
26//! DATABASE_URL=postgres://user:pass@localhost:5432/mydb
27//! # or for SQLite:
28//! DATABASE_URL=sqlite://./database.db
29//!
30//! # Optional:
31//! DB_MAX_CONNECTIONS=10
32//! DB_MIN_CONNECTIONS=1
33//! DB_CONNECT_TIMEOUT=30
34//! DB_LOGGING=false
35//! ```
36
37pub mod config;
38pub mod connection;
39pub mod eager_loading;
40pub mod model;
41pub mod query_builder;
42pub mod route_binding;
43pub mod testing;
44pub mod transaction;
45
46pub use config::{DatabaseConfig, DatabaseConfigBuilder, DatabaseType};
47pub use connection::DbConnection;
48pub use eager_loading::{batch_load_has_many, BatchLoad, BatchLoadMany};
49pub use model::{Model, ModelMut, Scope, ScopedQuery, ScopedQueryBuilder};
50pub use query_builder::QueryBuilder;
51pub use route_binding::{AutoRouteBinding, RouteBinding};
52pub use testing::TestDatabase;
53pub use transaction::{transaction, TransactionExt};
54
55/// Injectable database connection type
56///
57/// This is an alias for `DbConnection` that can be used with dependency injection.
58/// Use with the `#[inject]` attribute in actions and services for cleaner database access.
59///
60/// # Example
61///
62/// ```rust,ignore
63/// use ferro_rs::{injectable, Database};
64///
65/// #[injectable]
66/// pub struct CreateUserAction {
67///     #[inject]
68///     db: Database,
69/// }
70///
71/// impl CreateUserAction {
72///     pub async fn execute(&self) {
73///         let users = User::find().all(self.db.conn()).await?;
74///     }
75/// }
76/// ```
77pub type Database = DbConnection;
78
79use crate::error::FrameworkError;
80use crate::{App, Config};
81
82/// Database facade - main entry point for database operations
83///
84/// Provides static methods for initializing and accessing the database connection.
85/// The connection is stored in the application container as a singleton.
86///
87/// # Example
88///
89/// ```rust,ignore
90/// use ferro_rs::{DB, DatabaseConfig, Config};
91///
92/// // Initialize (usually in bootstrap.rs)
93/// Config::register(DatabaseConfig::from_env());
94/// DB::init().await?;
95///
96/// // Use anywhere in your app
97/// let conn = DB::connection()?;
98/// ```
99pub struct DB;
100
101impl DB {
102    /// Initialize the database connection
103    ///
104    /// Reads configuration from `DatabaseConfig` (must be registered via Config system)
105    /// and establishes a connection pool. The connection is stored in the App container.
106    ///
107    /// # Errors
108    ///
109    /// Returns an error if:
110    /// - `DatabaseConfig` is not registered
111    /// - Connection to the database fails
112    ///
113    /// # Example
114    ///
115    /// ```rust,ignore
116    /// // In bootstrap.rs
117    /// pub async fn register() {
118    ///     DB::init().await.expect("Failed to connect to database");
119    /// }
120    /// ```
121    pub async fn init() -> Result<(), FrameworkError> {
122        let config = Config::get::<DatabaseConfig>().ok_or_else(|| {
123            FrameworkError::internal(
124                "DatabaseConfig not registered. Call Config::register(DatabaseConfig::from_env()) first.",
125            )
126        })?;
127
128        let connection = DbConnection::connect(&config).await?;
129        App::singleton(connection);
130        Ok(())
131    }
132
133    /// Initialize with a custom config
134    ///
135    /// Useful for testing or when you need to connect to a different database.
136    ///
137    /// # Example
138    ///
139    /// ```rust,ignore
140    /// let config = DatabaseConfig::builder()
141    ///     .url("sqlite::memory:")
142    ///     .build();
143    /// DB::init_with(config).await?;
144    /// ```
145    pub async fn init_with(config: DatabaseConfig) -> Result<(), FrameworkError> {
146        let connection = DbConnection::connect(&config).await?;
147        App::singleton(connection);
148        Ok(())
149    }
150
151    /// Get the database connection
152    ///
153    /// Returns the connection from the App container. The connection is wrapped
154    /// in a `DbConnection` which provides convenient access to the underlying
155    /// SeaORM `DatabaseConnection`.
156    ///
157    /// # Errors
158    ///
159    /// Returns an error if `DB::init()` was not called.
160    ///
161    /// # Example
162    ///
163    /// ```rust,ignore
164    /// let conn = DB::connection()?;
165    ///
166    /// // Use with SeaORM queries
167    /// let users = User::find()
168    ///     .all(conn.inner())
169    ///     .await?;
170    /// ```
171    pub fn connection() -> Result<DbConnection, FrameworkError> {
172        App::resolve::<DbConnection>()
173    }
174
175    /// Check if the database connection is initialized
176    ///
177    /// # Example
178    ///
179    /// ```rust,ignore
180    /// if DB::is_connected() {
181    ///     let conn = DB::connection()?;
182    ///     // ...
183    /// }
184    /// ```
185    pub fn is_connected() -> bool {
186        App::has::<DbConnection>()
187    }
188
189    /// Get the database connection for use with SeaORM
190    ///
191    /// This is a convenience alias for `DB::connection()`. The returned
192    /// `DbConnection` implements `Deref<Target=DatabaseConnection>`, so you
193    /// can use it directly with SeaORM methods.
194    ///
195    /// # Example
196    ///
197    /// ```rust,ignore
198    /// use ferro_rs::database::DB;
199    /// use sea_orm::{Set, ActiveModelTrait};
200    ///
201    /// let new_todo = todos::ActiveModel {
202    ///     title: Set("My Todo".to_string()),
203    ///     ..Default::default()
204    /// };
205    ///
206    /// // Use &* to dereference to &DatabaseConnection
207    /// let inserted = new_todo.insert(&*DB::get()?).await?;
208    ///
209    /// // Or use .inner() method
210    /// let inserted = new_todo.insert(DB::get()?.inner()).await?;
211    /// ```
212    pub fn get() -> Result<DbConnection, FrameworkError> {
213        Self::connection()
214    }
215}
216
217// Re-export sea_orm types that users commonly need
218pub use sea_orm;