pub mod config;
mod error;
mod hygiene;
pub mod journal;
pub mod local;
mod state;
pub use crate::server_core::{Clock, Core, Policy, SystemClock, replay_journal};
pub use config::ServerConfig;
pub use error::ApiError;
pub use state::AppState;
use crate::proto::api::ErrorCode;
use crate::server_core::{CoreError, ROUTES, RouteId, Served};
use axum::Router;
use axum::extract::{Request, State};
use axum::middleware;
use axum::response::{IntoResponse, Response};
use axum::routing::get;
pub const EXPIRES_HEADER: &str = "x-gv-expires-at";
pub fn router(state: AppState) -> Router {
let mut router = Router::new();
for route in ROUTES.iter().filter(|r| r.served == Served::Shell) {
debug_assert_eq!(route.method, "GET", "the shell serves GET only");
router = match route.id {
RouteId::Healthz => router.route(route.path, get(health)),
RouteId::Readyz => router.route(route.path, get(ready)),
_ => router,
};
}
router
.fallback(to_core)
.method_not_allowed_fallback(method_not_allowed)
.layer(middleware::from_fn(hygiene::hygiene))
.with_state(state)
}
async fn to_core(State(state): State<AppState>, request: Request) -> Response {
let (parts, body) = request.into_parts();
let body = match axum::body::to_bytes(body, state.config().body_limit()).await {
Ok(body) => body,
Err(_) => {
return ApiError::new(ErrorCode::ValueTooLarge, "the request body is too large")
.into_response();
}
};
error::into_http(state.call(parts, body).await)
}
async fn health() -> axum::Json<serde_json::Value> {
axum::Json(serde_json::json!({ "status": "ok" }))
}
async fn ready(State(state): State<AppState>) -> Response {
if state.journal_ready().await {
axum::Json(serde_json::json!({ "status": "ready" })).into_response()
} else {
ApiError::new(ErrorCode::Unavailable, "the journal is unreachable").into_response()
}
}
async fn method_not_allowed() -> ApiError {
CoreError::method_not_allowed().into()
}