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
19#![deny(missing_docs)]
20#![deny(rustdoc::broken_intra_doc_links)]
21
22pub mod error;
23pub mod middleware;
24
25use axum::routing::get;
26use axum::{Json, Router};
27use serde_json::{json, Value};
28
29pub use error::ApiError;
30pub use middleware::{latency_slo, request_id, RequestId, SLO_BUDGET_MS};
31
32/// Standard health endpoint payload.
33pub async fn health() -> Json<Value> {
34    Json(json!({ "status": "ok" }))
35}
36
37/// Baseline metric shared by reference hosts. Products merge their own metric
38/// families into the same endpoint when they need richer observability.
39pub async fn metrics() -> ([(&'static str, &'static str); 1], &'static str) {
40    (
41        [("content-type", "text/plain; version=0.0.4")],
42        "agent_factory_up 1\n",
43    )
44}
45
46/// Wrap a router with the standard middleware stack: latency SLO (outermost,
47/// so it times everything) over request-id (so the id exists for SLO logs).
48pub fn with_standard_middleware<S>(router: Router<S>) -> Router<S>
49where
50    S: Clone + Send + Sync + 'static,
51{
52    router
53        .layer(axum::middleware::from_fn(latency_slo))
54        .layer(axum::middleware::from_fn(request_id))
55}
56
57/// A `Router` preseeded with `GET /health`. Services merge their routes onto it.
58pub fn base_router<S>() -> Router<S>
59where
60    S: Clone + Send + Sync + 'static,
61{
62    Router::new()
63        .route("/health", get(health))
64        .route("/metrics", get(metrics))
65}