af-web 0.3.0

Reusable axum web infrastructure: request-id + latency-SLO middleware, JSON error envelope, health. The cross-cutting half of agent_core/api/.
Documentation
//! Unit coverage for the error envelope + health + router helpers.
//! (The middleware is also exercised by the starter-product integration tests.)

use axum::http::StatusCode;
use axum::response::IntoResponse;

use af_web::{base_router, health, metrics, with_standard_middleware, ApiError};

#[test]
fn api_error_constructors_and_status() {
    assert_eq!(ApiError::bad_request("x").status, StatusCode::BAD_REQUEST);
    assert_eq!(ApiError::not_found("x").status, StatusCode::NOT_FOUND);
    assert_eq!(
        ApiError::internal("x").status,
        StatusCode::INTERNAL_SERVER_ERROR
    );

    let e = ApiError::new(StatusCode::CONFLICT, "dup").with_request_id("req_1");
    assert_eq!(e.status, StatusCode::CONFLICT);
    assert_eq!(e.request_id.as_deref(), Some("req_1"));

    // renders to a response with the right status
    let resp = e.into_response();
    assert_eq!(resp.status(), StatusCode::CONFLICT);
}

#[tokio::test]
async fn health_payload() {
    let axum::Json(v) = health().await;
    assert_eq!(v["status"], "ok");
}

#[tokio::test]
async fn metrics_payload() {
    let response = metrics().await;
    assert_eq!(response.1, "agent_factory_up 1\n");
}

#[test]
fn router_helpers_compose() {
    // base_router + middleware wrapper type-check and build.
    let _r: axum::Router = with_standard_middleware(base_router::<()>());
}