Skip to main content

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}