use std::sync::Arc;
use axum::{Extension, Router};
use crate::core::ModelSchema;
use crate::sql::{raw_execute_pool, Pool};
pub struct AppBuilder {
pool: Pool,
api: Option<Router>,
schemas: Vec<&'static ModelSchema>,
}
impl AppBuilder {
pub async fn from_env() -> Result<Self, Box<dyn std::error::Error>> {
let url = std::env::var("DATABASE_URL").map_err(|_| {
"DATABASE_URL not set. Examples:\n \
sqlite:./var/app.db?mode=rwc\n \
postgres://user:pass@localhost:5432/dbname\n \
mysql://user:pass@localhost:3306/dbname"
})?;
let pool = Pool::connect(&url).await?;
Ok(Self::from_pool(pool))
}
#[must_use]
pub fn from_pool(pool: Pool) -> Self {
Self {
pool,
api: None,
schemas: Vec::new(),
}
}
#[must_use]
pub fn pool(&self) -> &Pool {
&self.pool
}
pub async fn bootstrap(
mut self,
schemas: &[&'static ModelSchema],
) -> Result<Self, Box<dyn std::error::Error>> {
use crate::migrate::ddl;
let dialect = self.pool.dialect();
for schema in schemas {
let sql = ddl::create_table_sql_with_dialect(dialect, schema);
raw_execute_pool(&self.pool, &sql, vec![]).await?;
}
if dialect.name() != "sqlite" {
for schema in schemas {
for sql in ddl::create_constraints_sql_with_dialect(dialect, schema) {
raw_execute_pool(&self.pool, &sql, vec![]).await?;
}
}
}
self.schemas.extend(schemas);
Ok(self)
}
#[must_use]
pub fn api(mut self, router: Router) -> Self {
self.api = Some(router);
self
}
pub async fn serve(self, addr: &str) -> Result<(), Box<dyn std::error::Error>> {
let pool = Arc::new(self.pool);
let app = self.api.unwrap_or_else(Router::new).layer(Extension(pool));
let listener = tokio::net::TcpListener::bind(addr).await?;
axum::serve(listener, app).await?;
Ok(())
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "sqlite")]
use super::*;
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn from_pool_sqlite_in_memory_smoke() {
let sqlx_pool = crate::sql::sqlx::sqlite::SqlitePoolOptions::new()
.max_connections(1)
.connect("sqlite::memory:")
.await
.unwrap();
let pool: Pool = sqlx_pool.into();
let builder = AppBuilder::from_pool(pool);
assert_eq!(builder.pool().backend_name(), "sqlite");
assert!(builder.pool().as_sqlite().is_some());
}
}