feat(server): wire config, database, and HTTP serving
This commit is contained in:
@@ -3,3 +3,35 @@
|
|||||||
mod config;
|
mod config;
|
||||||
|
|
||||||
pub use config::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(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
fn main() {
|
use clap::Parser;
|
||||||
println!("placeholder");
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user