Skip to main content

molten_api/
startup.rs

1//! This module handles the application startup process for the Molten API.
2//!
3//! It includes logic for connecting to the database, running migrations,
4//! building the Axum application, and starting the HTTP server.
5use tokio::net::TcpListener;
6
7use crate::handlers;
8use crate::{error::BuildError, state::AppState};
9use axum::{
10    Router,
11    http::StatusCode,
12    routing::{get, post},
13};
14use molten_config::settings_parser::Settings;
15use sea_orm::{Database, DatabaseConnection, DbErr};
16
17/// Represents the Molten API application, encapsulating the server's network listener,
18/// application-wide state, and the port it is bound to.
19pub struct Application {
20    listener: TcpListener,
21    state: AppState,
22    port: u16,
23}
24
25impl Application {
26    /// Builds a new `Application` instance by connecting to the database,
27    /// running pending migrations, and setting up the TCP listener.
28    ///
29    /// # Arguments
30    /// * `config` - The application settings loaded from configuration.
31    ///
32    /// # Returns
33    /// A `Result` which is `Ok` with the `Application` instance if successful,
34    /// or `Err` with a `BuildError` if any step fails.
35    pub async fn build(config: Settings) -> Result<Self, BuildError> {
36        // Connect to Database
37        let db: DatabaseConnection = Self::get_db_connection(&config).await?;
38        tracing::info!("Connected to database: {}", &config.database.database_name);
39
40        let state = AppState::new(db);
41        let addr = format!("{}:{}", config.application.host, config.application.port);
42        tracing::info!("Listening on {}", addr);
43        let listener = TcpListener::bind(addr).await?;
44        let port = listener.local_addr().unwrap().port();
45
46        // Return Application
47        Ok(Self {
48            listener,
49            state,
50            port,
51        })
52    }
53
54    // This is useful because when the port in config is 0, a random port will be assigned
55    // which we need to know post hoc.
56    /// Port number getter
57    pub fn port(&self) -> u16 {
58        self.port
59    }
60
61    /// Establishes a database connection using the provided application settings.
62    ///
63    /// # Arguments
64    /// * `config` - A reference to the application settings.
65    ///
66    /// # Returns
67    /// A `Result` which is `Ok` with the `DatabaseConnection` if successful,
68    /// or `Err` with a `DbErr` if the connection fails.
69    async fn get_db_connection(config: &Settings) -> Result<DatabaseConnection, DbErr> {
70        Database::connect(config.database.get_connect_options()).await
71    }
72
73    /// Creates the Axum router with all routes and state attached.
74    ///
75    /// # Arguments
76    /// * `db` - The database connection
77    ///
78    /// # Returns
79    /// An axum Router
80    fn define_router(db: DatabaseConnection) -> Router {
81        let state = AppState::new(db);
82
83        Router::new()
84            .route("/health", get(|| async { StatusCode::OK }))
85            .route("/documents", post(handlers::create_document))
86            .route("/documents/{id}", get(handlers::get_document))
87            .route("/forms", post(handlers::create_form))
88            .route("/forms/{id}", get(handlers::get_form))
89            .route("/workflows", post(handlers::create_workflow))
90            .route("/workflows/{id}", get(handlers::get_workflow))
91            .with_state(state)
92    }
93
94    /// Runs the application
95    pub async fn run(self) -> Result<(), std::io::Error> {
96        let Application {
97            listener,
98            state,
99            port: _,
100        } = self;
101        let router = Self::define_router(state.db);
102        axum::serve(listener, router.into_make_service()).await
103    }
104}