diff --git a/crates/server/src/lib.rs b/crates/server/src/lib.rs index 30138bc..7d37361 100644 --- a/crates/server/src/lib.rs +++ b/crates/server/src/lib.rs @@ -3,3 +3,35 @@ mod config; pub use config::Config; + +use anyhow::Context; +use api::{AppState, build_app}; +use db::Db; +use tokio::net::TcpListener; + +/// Connect dependencies from `config` and serve until shutdown. +pub async fn run(config: Config) -> anyhow::Result<()> { + let db = Db::connect(&config.database_url) + .await + .context("connecting to the database")?; + let state = AppState { + db, + app_name: config.app_name.clone(), + }; + + let listener = TcpListener::bind(&config.bind_addr) + .await + .with_context(|| format!("binding to {}", config.bind_addr))?; + tracing::info!(addr = %config.bind_addr, "server listening"); + + serve(listener, state).await +} + +/// Serve the API on an already-bound listener (used by `run` and tests). +pub async fn serve(listener: TcpListener, state: AppState) -> anyhow::Result<()> { + let app = build_app(state); + axum::serve(listener, app) + .await + .context("running the HTTP server")?; + Ok(()) +} diff --git a/crates/server/src/main.rs b/crates/server/src/main.rs index 895e714..8c94771 100644 --- a/crates/server/src/main.rs +++ b/crates/server/src/main.rs @@ -1,3 +1,12 @@ -fn main() { - println!("placeholder"); +use clap::Parser; +use server::{Config, run}; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .init(); + + let config = Config::parse(); + run(config).await } diff --git a/crates/server/tests/serve.rs b/crates/server/tests/serve.rs new file mode 100644 index 0000000..cdad388 --- /dev/null +++ b/crates/server/tests/serve.rs @@ -0,0 +1,37 @@ +use std::net::SocketAddr; + +use api::AppState; +use db::Db; +use server::serve; +use tokio::net::TcpListener; + +#[tokio::test] +async fn serves_health_live_over_tcp() { + let database_url = + std::env::var("DATABASE_URL").expect("DATABASE_URL must be set for this test"); + let db = Db::connect(&database_url) + .await + .expect("connect to database"); + let state = AppState { + db, + app_name: "Test".to_string(), + }; + + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr: SocketAddr = listener.local_addr().unwrap(); + + let handle = tokio::spawn(async move { + serve(listener, state).await.unwrap(); + }); + + let url = format!("http://{addr}/health/live"); + let body: serde_json::Value = reqwest::get(&url) + .await + .expect("request succeeds") + .json() + .await + .expect("json body"); + assert_eq!(body["status"], "ok"); + + handle.abort(); +}