1pub mod admin;
2pub mod admin_api;
3pub mod admin_resources;
4pub mod columnar;
5pub mod hnsw;
6pub mod kv;
7pub mod session;
8pub mod sql;
9pub mod vector;
10
11use std::sync::atomic::Ordering;
12use std::sync::Arc;
13
14use axum::http::{HeaderValue, StatusCode};
15use axum::middleware;
16use axum::response::{IntoResponse, Response};
17use axum::{Json, Router};
18use serde::Serialize;
19use tower::ServiceBuilder;
20use tower_http::limit::RequestBodyLimitLayer;
21use tower_http::trace::TraceLayer;
22use tracing::Span;
23use uuid::Uuid;
24
25use crate::auth::AuthError;
26use crate::error::ServerError;
27use crate::server::ServerState;
28
29#[derive(Clone, Debug)]
30pub struct RequestContext {
31 pub correlation_id: String,
32 pub actor: Option<String>,
33}
34
35#[derive(Serialize)]
36struct ErrorBody {
37 code: String,
38 message: String,
39 correlation_id: String,
40}
41
42#[derive(Serialize)]
43struct ErrorResponse {
44 error: ErrorBody,
45}
46
47struct QueueWaitGuard<'a> {
48 counter: &'a std::sync::atomic::AtomicUsize,
49}
50
51impl<'a> QueueWaitGuard<'a> {
52 fn new(counter: &'a std::sync::atomic::AtomicUsize) -> Self {
53 Self { counter }
54 }
55}
56
57impl Drop for QueueWaitGuard<'_> {
58 fn drop(&mut self) {
59 self.counter.fetch_sub(1, Ordering::AcqRel);
60 }
61}
62
63pub fn router(state: Arc<ServerState>) -> Router {
64 let api = Router::new()
65 .route("/kv/get", axum::routing::post(kv::get))
66 .route("/kv/put", axum::routing::post(kv::put))
67 .route("/kv/delete", axum::routing::post(kv::delete))
68 .route("/kv/list", axum::routing::post(kv::list))
69 .route("/kv/txn/begin", axum::routing::post(kv::txn_begin))
70 .route("/kv/txn/get", axum::routing::post(kv::txn_get))
71 .route("/kv/txn/put", axum::routing::post(kv::txn_put))
72 .route("/kv/txn/delete", axum::routing::post(kv::txn_delete))
73 .route("/kv/txn/commit", axum::routing::post(kv::txn_commit))
74 .route("/kv/txn/rollback", axum::routing::post(kv::txn_rollback))
75 .route("/columnar/scan", axum::routing::post(columnar::scan))
76 .route("/columnar/stats", axum::routing::post(columnar::stats))
77 .route("/columnar/list", axum::routing::post(columnar::list))
78 .route("/columnar/ingest", axum::routing::post(columnar::ingest))
79 .route(
80 "/columnar/index/create",
81 axum::routing::post(columnar::index_create),
82 )
83 .route(
84 "/columnar/index/list",
85 axum::routing::post(columnar::index_list),
86 )
87 .route(
88 "/columnar/index/drop",
89 axum::routing::post(columnar::index_drop),
90 )
91 .route("/hnsw/search", axum::routing::post(hnsw::search))
92 .route("/hnsw/upsert", axum::routing::post(hnsw::upsert))
93 .route("/hnsw/delete", axum::routing::post(hnsw::delete))
94 .route("/hnsw/create", axum::routing::post(hnsw::create))
95 .route("/hnsw/drop", axum::routing::post(hnsw::drop))
96 .route("/hnsw/stats", axum::routing::post(hnsw::stats))
97 .route("/sql", axum::routing::post(sql::handle))
98 .route("/api/sql/query", axum::routing::post(sql::handle))
99 .route("/vector/search", axum::routing::post(vector::search))
100 .route("/vector/upsert", axum::routing::post(vector::upsert))
101 .route("/vector/delete", axum::routing::post(vector::delete))
102 .route(
103 "/vector/index/create",
104 axum::routing::post(vector::index_create),
105 )
106 .route(
107 "/vector/index/update",
108 axum::routing::post(vector::index_update),
109 )
110 .route(
111 "/vector/index/delete",
112 axum::routing::post(vector::index_delete),
113 )
114 .route(
115 "/vector/index/compact",
116 axum::routing::post(vector::index_compact),
117 )
118 .route(
119 "/api/admin/capabilities",
120 axum::routing::get(admin_api::capabilities),
121 )
122 .route(
123 "/api/admin/resources",
124 axum::routing::get(admin_resources::list),
125 )
126 .route("/api/admin/status", axum::routing::get(admin_api::status))
127 .route("/api/admin/metrics", axum::routing::get(admin_api::metrics))
128 .route("/api/admin/health", axum::routing::get(admin_api::health))
129 .route(
130 "/api/admin/cluster/join",
131 axum::routing::post(admin_api::cluster_join),
132 )
133 .route(
134 "/api/admin/cluster/leave",
135 axum::routing::post(admin_api::cluster_leave),
136 )
137 .route(
138 "/api/admin/cluster/metadata",
139 axum::routing::get(admin_api::cluster_metadata),
140 )
141 .route(
142 "/api/admin/cluster/operations",
143 axum::routing::post(admin_api::cluster_management),
144 )
145 .route(
146 "/api/admin/backup",
147 axum::routing::post(admin_api::start_backup),
148 )
149 .route("/api/admin/export", axum::routing::post(admin_api::export))
150 .route(
151 "/api/admin/backup/{id}",
152 axum::routing::get(admin_api::backup_status),
153 )
154 .route(
155 "/api/admin/restore",
156 axum::routing::post(admin_api::start_restore),
157 )
158 .route(
159 "/api/admin/restore/{id}",
160 axum::routing::get(admin_api::restore_status),
161 )
162 .route(
163 "/api/admin/lifecycle",
164 axum::routing::post(admin_api::lifecycle),
165 )
166 .route(
167 "/api/admin/compaction",
168 axum::routing::post(admin_api::compaction),
169 )
170 .route("/session/begin", axum::routing::post(session::begin))
171 .route("/session/{id}/commit", axum::routing::post(session::commit))
172 .route(
173 "/session/{id}/rollback",
174 axum::routing::post(session::rollback),
175 );
176
177 let api = if state.config.api_prefix.is_empty() {
178 api
179 } else {
180 Router::new().nest(&state.config.api_prefix, api)
181 };
182
183 let api = api
188 .route(
189 "/v1/sql/reads",
190 axum::routing::post(sql::begin_distributed_read),
191 )
192 .route(
193 "/v1/sql/reads/{id}",
194 axum::routing::get(sql::stream_distributed_read),
195 )
196 .route(
197 "/v1/sql/reads/{id}/cancel",
198 axum::routing::post(sql::cancel_distributed_read),
199 );
200
201 let middleware = middleware::from_fn(context_middleware);
202 let connection_middleware = middleware::from_fn(connection_middleware);
203 let admission_middleware = middleware::from_fn(admission_middleware);
204 api.layer(
211 ServiceBuilder::new()
212 .layer(TraceLayer::new_for_http().make_span_with(make_trace_span))
213 .layer(admission_middleware)
214 .layer(middleware)
215 .layer(connection_middleware)
216 .layer(RequestBodyLimitLayer::new(state.config.max_request_size)),
217 )
218 .layer(axum::Extension(state))
219}
220
221pub fn admin_router(state: Arc<ServerState>) -> Router {
222 admin::router(state)
223}
224
225pub async fn context_middleware(
226 axum::extract::Extension(state): axum::extract::Extension<Arc<ServerState>>,
227 mut req: axum::extract::Request,
228 next: middleware::Next,
229) -> Response {
230 let correlation_id =
231 extract_correlation_id(req.headers()).unwrap_or_else(|| Uuid::new_v4().to_string());
232
233 let actor = match state.auth.validate_http(req.headers()) {
234 Ok(actor) => actor,
235 Err(err) => {
236 if state.config.audit_log_enabled {
237 state.audit.log(crate::audit::AuditLogEntry {
238 event_type: crate::audit::AuditEventType::AuthFailure,
239 actor: None,
240 target: "auth".into(),
241 correlation_id: correlation_id.clone(),
242 timestamp: chrono::Utc::now(),
243 details: serde_json::json!({ "error": err.to_string() }),
244 });
245 }
246 return auth_error_response(err, &correlation_id);
247 }
248 };
249
250 req.extensions_mut().insert(RequestContext {
251 correlation_id: correlation_id.clone(),
252 actor,
253 });
254
255 let mut res = next.run(req).await;
256 let _ = res.headers_mut().insert(
257 "x-correlation-id",
258 HeaderValue::from_str(&correlation_id).unwrap_or_else(|_| HeaderValue::from_static("")),
259 );
260 res
261}
262
263pub async fn connection_middleware(
264 axum::extract::Extension(state): axum::extract::Extension<Arc<ServerState>>,
265 req: axum::extract::Request,
266 next: middleware::Next,
267) -> Response {
268 state.metrics.record_connection(1);
269 let res = next.run(req).await;
270 state.metrics.record_connection(-1);
271 res
272}
273
274pub async fn admission_middleware(
275 axum::extract::Extension(state): axum::extract::Extension<Arc<ServerState>>,
276 req: axum::extract::Request,
277 next: middleware::Next,
278) -> Response {
279 if let Ok(permit) = state.admission_permits.clone().try_acquire_owned() {
280 let res = next.run(req).await;
281 drop(permit);
282 return res;
283 }
284
285 let queued_now = state.admission_waiters.fetch_add(1, Ordering::AcqRel) + 1;
286 if queued_now > state.config.max_queue_len {
287 state.admission_waiters.fetch_sub(1, Ordering::AcqRel);
288 let correlation_id =
289 extract_correlation_id(req.headers()).unwrap_or_else(|| Uuid::new_v4().to_string());
290 return queue_overflow_response(&correlation_id);
291 }
292 let queue_wait_guard = QueueWaitGuard::new(&state.admission_waiters);
293
294 let permit = match state.admission_permits.clone().acquire_owned().await {
295 Ok(permit) => permit,
296 Err(_) => {
297 let correlation_id =
298 extract_correlation_id(req.headers()).unwrap_or_else(|| Uuid::new_v4().to_string());
299 return queue_overflow_response(&correlation_id);
300 }
301 };
302 drop(queue_wait_guard);
303
304 let res = next.run(req).await;
305 drop(permit);
306 res
307}
308
309fn auth_error_response(err: AuthError, correlation_id: &str) -> Response {
310 let message = err.to_string();
311 let body = Json(ErrorResponse {
312 error: ErrorBody {
313 code: "UNAUTHORIZED".to_string(),
314 message,
315 correlation_id: correlation_id.to_string(),
316 },
317 });
318 (StatusCode::UNAUTHORIZED, body).into_response()
319}
320
321fn queue_overflow_response(correlation_id: &str) -> Response {
322 let body = Json(ErrorResponse {
323 error: ErrorBody {
324 code: "SERVER_BACKPRESSURE".to_string(),
325 message: "server request queue is full".to_string(),
326 correlation_id: correlation_id.to_string(),
327 },
328 });
329 (StatusCode::SERVICE_UNAVAILABLE, body).into_response()
330}
331
332pub fn error_response(err: ServerError, ctx: &RequestContext) -> Response {
333 let body = Json(ErrorResponse {
334 error: ErrorBody {
335 code: err.error_code(),
336 message: err.to_string(),
337 correlation_id: ctx.correlation_id.clone(),
338 },
339 });
340 (err.status_code(), body).into_response()
341}
342
343fn make_trace_span<B>(request: &axum::http::Request<B>) -> Span {
344 let correlation_id = request
345 .extensions()
346 .get::<RequestContext>()
347 .map(|ctx| ctx.correlation_id.clone())
348 .or_else(|| extract_correlation_id(request.headers()))
349 .unwrap_or_else(|| Uuid::new_v4().to_string());
350 let traceparent = request
351 .headers()
352 .get("traceparent")
353 .and_then(|v| v.to_str().ok())
354 .unwrap_or("");
355 tracing::info_span!(
356 "http_request",
357 correlation_id = %correlation_id,
358 traceparent = %traceparent,
359 method = %request.method(),
360 path = %request.uri().path()
361 )
362}
363
364pub fn json_response<T: Serialize>(value: T, max_size: usize, ctx: &RequestContext) -> Response {
365 match serde_json::to_vec(&value) {
366 Ok(bytes) if bytes.len() <= max_size => (StatusCode::OK, Json(value)).into_response(),
367 Ok(_) => error_response(
368 ServerError::PayloadTooLarge("response size exceeds limit".into()),
369 ctx,
370 ),
371 Err(err) => error_response(ServerError::Internal(err.to_string()), ctx),
372 }
373}
374
375fn extract_correlation_id(headers: &axum::http::HeaderMap) -> Option<String> {
376 headers
377 .get("x-correlation-id")
378 .and_then(|v| v.to_str().ok())
379 .map(|v| v.to_string())
380 .or_else(|| {
381 headers
382 .get("x-request-id")
383 .and_then(|v| v.to_str().ok())
384 .map(|v| v.to_string())
385 })
386}