af_web/lib.rs
1//! `af-web` — reusable axum web infrastructure (the cross-cutting half of
2//! `agent_core/api/`). Provides:
3//!
4//! - [`ApiError`] — JSON error envelope carrying the request id.
5//! - [`middleware::request_id`] / [`middleware::latency_slo`] — request-id
6//! propagation and the API latency SLO (warn past [`middleware::SLO_BUDGET_MS`]).
7//! - [`with_standard_middleware`] — wrap any `Router` with the standard stack.
8//! - [`health`] — a ready-made health handler.
9//! - [`metrics`] — a minimal Prometheus-compatible liveness metric.
10//!
11//! A service composes its product routes and wraps them:
12//!
13//! ```ignore
14//! let app = with_standard_middleware(
15//! Router::new().route("/health", get(health)).merge(product_routes),
16//! );
17//! ```
18
19pub mod error;
20pub mod middleware;
21
22use axum::routing::get;
23use axum::{Json, Router};
24use serde_json::{json, Value};
25
26pub use error::ApiError;
27pub use middleware::{latency_slo, request_id, RequestId, SLO_BUDGET_MS};
28
29/// Standard health endpoint payload.
30pub async fn health() -> Json<Value> {
31 Json(json!({ "status": "ok" }))
32}
33
34/// Baseline metric shared by reference hosts. Products merge their own metric
35/// families into the same endpoint when they need richer observability.
36pub async fn metrics() -> ([(&'static str, &'static str); 1], &'static str) {
37 (
38 [("content-type", "text/plain; version=0.0.4")],
39 "agent_factory_up 1\n",
40 )
41}
42
43/// Wrap a router with the standard middleware stack: latency SLO (outermost,
44/// so it times everything) over request-id (so the id exists for SLO logs).
45pub fn with_standard_middleware<S>(router: Router<S>) -> Router<S>
46where
47 S: Clone + Send + Sync + 'static,
48{
49 router
50 .layer(axum::middleware::from_fn(latency_slo))
51 .layer(axum::middleware::from_fn(request_id))
52}
53
54/// A `Router` preseeded with `GET /health`. Services merge their routes onto it.
55pub fn base_router<S>() -> Router<S>
56where
57 S: Clone + Send + Sync + 'static,
58{
59 Router::new()
60 .route("/health", get(health))
61 .route("/metrics", get(metrics))
62}