antithesis_intake/
http.rs

1//! Axum HTTP surface for the intake.
2//!
3//! This module composes the full router while the submodules keep protocol
4//! groups separate:
5//!
6//! - `datadog`: Datadog-compatible intake and health routes:
7//!   - `POST /api/v2/series`
8//!   - `POST /api/beta/sketches`
9//!   - `POST /api/v1/events_batch`
10//!   - `POST /api/v1/events`
11//!   - `POST /intake/`
12//!   - `POST /api/v1/check_run`
13//!   - `GET /api/v1/validate`
14//! - `antithesis`: private scenario-control routes used by Antithesis drivers:
15//!   - `GET /antithesis/metrics/{target}`
16//! - `middleware`: request body measurement used by payload assertions.
17//! - `state`: shared router state for one capture target.
18
19mod antithesis;
20mod datadog;
21pub(crate) mod middleware;
22pub mod state;
23
24use axum::{http::StatusCode, Router};
25use http_body_util::LengthLimitError;
26
27use self::state::AppState;
28
29/// Memory backstop on the compressed body buffered before decompression. Sits above any Pyld05 spec limit.
30const MAX_COMPRESSED_BODY_BYTES: usize = 64 * 1024 * 1024;
31
32/// Caps the decompressed body a handler buffers. Exceeds every Pyld06 spec limit.
33const MAX_DECOMPRESSED_BODY_BYTES: usize = 64 * 1024 * 1024;
34
35/// Build the intake router. `/api/v2/series` fires payload assertions. Datadog endpoints answer
36/// 202. A malformed body gets 400. An oversized body gets 413. Unmatched paths answer 200.
37pub fn build_router(state: AppState) -> Router {
38    Router::new()
39        .merge(datadog::routes())
40        .merge(antithesis::routes())
41        .fallback(|| async { StatusCode::OK })
42        .with_state(state)
43}
44
45/// Whether a body read failed because it overran the byte cap rather than because the read itself
46/// failed. Only the cap is the producer's fault: a mid-body read failure is what an injected network
47/// fault looks like, and blaming a size property for that would redden a lane the fault broke.
48pub(crate) fn body_over_cap(e: axum::Error) -> bool {
49    e.into_inner().downcast_ref::<LengthLimitError>().is_some()
50}
51
52#[cfg(test)]
53mod tests {
54    use axum::body::{to_bytes, Body};
55
56    use super::body_over_cap;
57
58    // The cap error comes from axum's own read path rather than a hand-built one, so this pins how
59    // axum wraps it. A read failure must not be mistaken for it.
60    #[tokio::test]
61    async fn body_over_cap_separates_the_cap_from_a_read_failure() {
62        let over_cap = to_bytes(Body::from("ab"), 1).await.expect_err("body exceeds the cap");
63        assert!(body_over_cap(over_cap));
64        assert!(!body_over_cap(axum::Error::new(std::io::Error::other(
65            "connection reset by peer"
66        ))));
67    }
68}