sword_ai/
server.rs

1//! Server execution module.
2//!
3//! Provides functions to run an Axum server with database connectivity
4//! and optional automatic migrations.
5//!
6//! ## Functions
7//!
8//! - [`run`] - Run server without migrations
9//! - [`run_with_migrator`] - Run server with optional migrations
10//!
11//! ## Example
12//!
13//! ```rust,ignore
14//! use sword_ai::server::run_with_migrator;
15//! use axum::Router;
16//!
17//! run_with_migrator::<Migrator, _>(|ctx| {
18//!     Router::new().with_state(ctx.clone())
19//! }, true).await?;
20//! ```
21
22use crate::config::AppConfig;
23use crate::db;
24use axum::Router;
25use sea_orm::DatabaseConnection;
26use sea_orm_migration::MigratorTrait;
27
28/// Shared context available to all route handlers.
29///
30/// Contains the application configuration and database connection.
31/// Clone this to pass it as Axum state.
32#[derive(Clone)]
33pub struct FrameworkContext {
34    /// Application configuration.
35    pub config: AppConfig,
36    /// Database connection pool.
37    pub db: DatabaseConnection,
38}
39
40/// Runs the Axum server without database migrations.
41///
42/// # Arguments
43///
44/// * `build_router` - A function that receives a [`FrameworkContext`] and returns a configured [`Router`].
45///
46/// # Example
47///
48/// ```rust,ignore
49/// use sword_ai::server::run;
50/// use axum::{Router, routing::get};
51///
52/// run(|ctx| {
53///     Router::new()
54///         .route("/health", get(|| async { "OK" }))
55///         .with_state(ctx.clone())
56/// }).await?;
57/// ```
58pub async fn run<F>(build_router: F) -> anyhow::Result<()>
59where
60    F: FnOnce(&FrameworkContext) -> Router,
61{
62    let config = AppConfig::from_env()?;
63    let db = db::connect_db(&config).await?;
64
65    let ctx = FrameworkContext {
66        config: config.clone(),
67        db,
68    };
69
70    let app = build_router(&ctx);
71
72    let bind_addr = config.bind_address();
73    tracing::info!("Starting server on {}", bind_addr);
74
75    let listener = tokio::net::TcpListener::bind(&bind_addr).await?;
76    axum::serve(listener, app).await?;
77
78    Ok(())
79}
80
81/// Runs the Axum server with optional database migrations.
82///
83/// # Arguments
84///
85/// * `build_router` - A function that receives a [`FrameworkContext`] and returns a configured [`Router`].
86/// * `run_migrations` - If `true`, runs pending migrations before starting the server.
87///
88/// # Type Parameters
89///
90/// * `M` - A type implementing [`MigratorTrait`] from SeaORM.
91///
92/// # Example
93///
94/// ```rust,ignore
95/// use sword_ai::server::run_with_migrator;
96/// use axum::{Router, routing::get};
97///
98/// run_with_migrator::<Migrator, _>(|ctx| {
99///     Router::new()
100///         .route("/health", get(|| async { "OK" }))
101///         .with_state(ctx.clone())
102/// }, true).await?;
103/// ```
104pub async fn run_with_migrator<M, F>(build_router: F, run_migrations: bool) -> anyhow::Result<()>
105where
106    M: MigratorTrait,
107    F: FnOnce(&FrameworkContext) -> Router,
108{
109    let config = AppConfig::from_env()?;
110    let db = db::connect_db(&config).await?;
111
112    if run_migrations {
113        tracing::info!("Running database migrations...");
114        M::up(&db, None).await?;
115        tracing::info!("Migrations completed successfully");
116    }
117
118    let ctx = FrameworkContext {
119        config: config.clone(),
120        db,
121    };
122
123    let app = build_router(&ctx);
124
125    let bind_addr = config.bind_address();
126    tracing::info!("Starting server on {}", bind_addr);
127
128    let listener = tokio::net::TcpListener::bind(&bind_addr).await?;
129    axum::serve(listener, app).await?;
130
131    Ok(())
132}