#![forbid(unsafe_code)]
#![warn(clippy::pedantic, missing_docs)]
#![allow(clippy::module_name_repetitions)]
use std::net::SocketAddr;
use std::path::PathBuf;
use axum::Router;
use serde::{Deserialize, Serialize};
pub mod db;
pub mod error;
pub mod middleware;
pub mod routes;
pub mod seed;
pub mod state;
pub use error::{Result, SimError};
pub use state::AppState;
pub const CAPABILITIES: reposix_core::BackendCapabilities = reposix_core::BackendCapabilities::new(
true,
true,
true,
true,
reposix_core::CommentSupport::InBody,
reposix_core::VersioningModel::Strong,
);
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SimConfig {
pub bind: SocketAddr,
pub db_path: PathBuf,
pub seed: bool,
#[serde(default)]
pub seed_file: Option<PathBuf>,
#[serde(default)]
pub ephemeral: bool,
#[serde(default = "default_rate_limit_rps")]
pub rate_limit_rps: u32,
}
fn default_rate_limit_rps() -> u32 {
100
}
impl SimConfig {
#[must_use]
pub fn ephemeral() -> Self {
Self {
bind: "127.0.0.1:0".parse().expect("static addr parses"),
db_path: PathBuf::from(":memory:"),
seed: true,
seed_file: None,
ephemeral: true,
rate_limit_rps: default_rate_limit_rps(),
}
}
}
pub fn build_router(state: AppState, rate_limit_rps: u32) -> Router {
let handlers = Router::new()
.route("/healthz", axum::routing::get(healthz))
.merge(routes::router(state.clone()));
let with_rate_limit = middleware::rate_limit::attach(handlers, rate_limit_rps);
middleware::audit::attach(with_rate_limit, state)
}
#[allow(clippy::unused_async)]
async fn healthz() -> &'static str {
"ok"
}
pub fn prepare_state(cfg: &SimConfig) -> Result<AppState> {
let conn = db::open_db(&cfg.db_path, cfg.ephemeral)?;
if cfg.seed {
if let Some(ref path) = cfg.seed_file {
let inserted = seed::load_seed(&conn, path)?;
tracing::info!(inserted, path = %path.display(), "seed loaded");
}
}
Ok(AppState::new(conn, cfg.clone()))
}
pub async fn run_with_listener(listener: tokio::net::TcpListener, cfg: SimConfig) -> Result<()> {
let state = prepare_state(&cfg)?;
tracing::info!(addr = %listener.local_addr()?, "reposix-sim listening");
axum::serve(listener, build_router(state, cfg.rate_limit_rps)).await?;
Ok(())
}
pub async fn run(cfg: SimConfig) -> Result<()> {
let listener = tokio::net::TcpListener::bind(cfg.bind)
.await
.map_err(|source| SimError::Bind {
addr: cfg.bind.to_string(),
source,
})?;
run_with_listener(listener, cfg).await
}