Skip to main content

uls_api/
server.rs

1//! Server configuration and router construction.
2
3use std::sync::Arc;
4
5use axum::Router;
6use tower_http::cors::{Any, CorsLayer};
7use tower_http::trace::TraceLayer;
8use uls_query::QueryEngine;
9
10use crate::handlers::{self, AppState};
11
12/// Configuration for the API server.
13#[derive(Debug, Clone)]
14pub struct ServerConfig {
15    /// Bind address (e.g., "127.0.0.1").
16    pub bind: String,
17    /// Listen port.
18    pub port: u16,
19    /// Allowed CORS origins (empty = no CORS layer).
20    pub cors_origins: Vec<String>,
21}
22
23impl Default for ServerConfig {
24    fn default() -> Self {
25        Self {
26            bind: "127.0.0.1".to_string(),
27            port: 3000,
28            cors_origins: Vec::new(),
29        }
30    }
31}
32
33/// Build the axum router with all routes and middleware.
34pub fn build_router(engine: QueryEngine, config: &ServerConfig) -> Router {
35    let state: AppState = Arc::new(engine);
36
37    let mut app = Router::new()
38        .route("/health", axum::routing::get(handlers::health))
39        .route("/stats", axum::routing::get(handlers::stats))
40        .route("/licenses/{callsign}", axum::routing::get(handlers::lookup))
41        .route("/licenses", axum::routing::get(handlers::search))
42        .route("/frn/{frn}", axum::routing::get(handlers::frn_lookup))
43        .with_state(state)
44        .layer(TraceLayer::new_for_http());
45
46    if !config.cors_origins.is_empty() {
47        let cors = if config.cors_origins.iter().any(|o| o == "*") {
48            CorsLayer::new().allow_origin(Any)
49        } else {
50            let origins: Vec<_> = config
51                .cors_origins
52                .iter()
53                .filter_map(|o| o.parse().ok())
54                .collect();
55            CorsLayer::new().allow_origin(origins)
56        };
57        app = app.layer(cors);
58    }
59
60    app
61}
62
63/// Start the server with the given configuration.
64pub async fn run(engine: QueryEngine, config: ServerConfig) -> std::io::Result<()> {
65    let app = build_router(engine, &config);
66    let addr = format!("{}:{}", config.bind, config.port);
67
68    tracing::info!("Starting ULS API server on {}", addr);
69
70    let listener = tokio::net::TcpListener::bind(&addr).await?;
71    axum::serve(listener, app)
72        .await
73        .expect("server should not fail");
74    Ok(())
75}