use exo_gateway::server::{GatewayConfig, parse_trusted_rate_limit_proxy_ips, serve};
use tracing_subscriber::EnvFilter;
fn init_tracing() {
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
tracing_subscriber::fmt()
.with_env_filter(filter)
.json()
.flatten_event(true)
.with_current_span(true)
.with_span_list(true)
.init();
}
#[tokio::main]
async fn main() {
init_tracing();
let bind_address = std::env::var("BIND_ADDRESS").unwrap_or_else(|_| "127.0.0.1:8443".into());
let trusted_rate_limit_proxy_ips = match std::env::var("TRUSTED_RATE_LIMIT_PROXY_IPS") {
Ok(raw) => match parse_trusted_rate_limit_proxy_ips(&raw) {
Ok(ips) => ips,
Err(error) => {
tracing::error!("Invalid TRUSTED_RATE_LIMIT_PROXY_IPS: {error}");
std::process::exit(1);
}
},
Err(_) => Default::default(),
};
let config = GatewayConfig {
bind_address,
trusted_rate_limit_proxy_ips,
..GatewayConfig::default()
};
let pool = match std::env::var("DATABASE_URL") {
Ok(url) => {
tracing::info!("Connecting to PostgreSQL…");
match exo_gateway::db::init_pool(&url).await {
Ok(pool) => {
tracing::info!("Database pool ready");
Some(pool)
}
Err(error) => {
tracing::error!("Database initialization failed: {error}");
std::process::exit(1);
}
}
}
Err(_) => {
tracing::warn!(
"DATABASE_URL not set — starting without database pool. \
/ready will return 503 until a pool is configured."
);
None
}
};
tracing::info!("Starting exo-gateway on {}", config.bind_address);
if let Err(e) = serve(config, pool).await {
tracing::error!("Gateway terminated with error: {e}");
std::process::exit(1);
}
}
#[cfg(test)]
mod tests {
const SOURCE: &str = include_str!("main.rs");
fn init_tracing_source() -> &'static str {
match SOURCE
.split("fn init_tracing()")
.nth(1)
.and_then(|section| section.split("#[tokio::main]").next())
{
Some(source) => source,
None => panic!("init_tracing must appear before main"),
}
}
#[test]
fn gateway_tracing_uses_env_filter_and_json_output() {
let production = match SOURCE.split("#[cfg(test)]").next() {
Some(source) => source,
None => panic!("production source precedes tests"),
};
let init_tracing = init_tracing_source();
let bare_fmt_init = concat!("tracing_subscriber::fmt", "::init()");
assert!(
!production.contains(bare_fmt_init),
"gateway runtime must not use bare tracing_subscriber::fmt::init()"
);
assert!(
init_tracing.contains("EnvFilter::try_from_default_env"),
"gateway runtime logging must honor RUST_LOG via EnvFilter"
);
assert!(
init_tracing.contains(".with_env_filter("),
"gateway runtime logging must attach the EnvFilter to the subscriber"
);
assert!(
init_tracing.contains(".json()"),
"gateway runtime logging must emit structured JSON"
);
}
#[test]
fn gateway_main_parses_trusted_rate_limit_proxy_configuration() {
let production = match SOURCE.split("#[cfg(test)]").next() {
Some(source) => source,
None => panic!("production source precedes tests"),
};
assert!(
production.contains("TRUSTED_RATE_LIMIT_PROXY_IPS"),
"gateway runtime must expose explicit trusted proxy rate-limit configuration"
);
assert!(
production.contains("parse_trusted_rate_limit_proxy_ips(&raw)"),
"gateway runtime must parse trusted proxy IPs through the fail-closed server parser"
);
assert!(
production.contains("std::process::exit(1)"),
"invalid trusted proxy configuration must fail closed at startup"
);
}
}