diff --git a/Cargo.lock b/Cargo.lock index 0172c49..9eafcf9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4700,7 +4700,6 @@ dependencies = [ "tower-http", "tracing", "tracing-subscriber", - "ulid", "utoipa", ] diff --git a/crates/omnigraph-server/Cargo.toml b/crates/omnigraph-server/Cargo.toml index 548330f..0f938a6 100644 --- a/crates/omnigraph-server/Cargo.toml +++ b/crates/omnigraph-server/Cargo.toml @@ -31,7 +31,6 @@ serde_yaml = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true } tower-http = { workspace = true } -ulid = { workspace = true } utoipa = { workspace = true } cedar-policy = { workspace = true } futures = { workspace = true } diff --git a/crates/omnigraph-server/src/lib.rs b/crates/omnigraph-server/src/lib.rs index 8e048e3..19d374f 100644 --- a/crates/omnigraph-server/src/lib.rs +++ b/crates/omnigraph-server/src/lib.rs @@ -2,7 +2,6 @@ pub mod api; pub mod auth; pub mod config; pub mod policy; -pub mod request_id; use std::collections::{HashMap, HashSet}; use std::fs; @@ -461,7 +460,6 @@ pub fn build_app(state: AppState) -> Router { .merge(protected) .layer(DefaultBodyLimit::max(DEFAULT_REQUEST_BODY_LIMIT_BYTES)) .layer(TraceLayer::new_for_http()) - .layer(middleware::from_fn(request_id::request_id_middleware)) .with_state(state) } diff --git a/crates/omnigraph-server/src/request_id.rs b/crates/omnigraph-server/src/request_id.rs deleted file mode 100644 index 3d68a0f..0000000 --- a/crates/omnigraph-server/src/request_id.rs +++ /dev/null @@ -1,59 +0,0 @@ -//! `X-Request-Id` middleware. -//! -//! Mints a ULID per inbound request, or echoes a caller-supplied -//! `X-Request-Id` header if it's well-formed. Stores the value in request -//! extensions so handlers can include it in error bodies, log lines, or -//! audit records, and surfaces it on the response header so SDK clients -//! can correlate logs across the wire. - -use axum::{ - body::Body, - extract::Request, - http::{HeaderName, HeaderValue}, - middleware::Next, - response::Response, -}; -use ulid::Ulid; - -pub const X_REQUEST_ID: HeaderName = HeaderName::from_static("x-request-id"); - -/// Wraps a request id pulled out of (or minted into) request extensions. -#[derive(Clone, Debug)] -pub struct RequestId(pub String); - -impl RequestId { - pub fn as_str(&self) -> &str { - &self.0 - } -} - -/// Acceptable inbound `X-Request-Id` shape: 1..=128 ASCII printable chars. -/// Rejecting wider input keeps the value safe to log and emit verbatim. -fn is_valid_inbound(raw: &str) -> bool { - !raw.is_empty() - && raw.len() <= 128 - && raw - .bytes() - .all(|b| b.is_ascii_graphic() || b == b' ' || b == b'-' || b == b'_') -} - -pub async fn request_id_middleware(mut req: Request, next: Next) -> Response { - let inbound = req - .headers() - .get(&X_REQUEST_ID) - .and_then(|v| v.to_str().ok()) - .filter(|raw| is_valid_inbound(raw)); - - let id = match inbound { - Some(raw) => raw.to_owned(), - None => Ulid::new().to_string(), - }; - - req.extensions_mut().insert(RequestId(id.clone())); - - let mut response = next.run(req).await; - if let Ok(value) = HeaderValue::from_str(&id) { - response.headers_mut().insert(X_REQUEST_ID, value); - } - response -} diff --git a/crates/omnigraph-server/tests/server.rs b/crates/omnigraph-server/tests/server.rs index a81141d..77b9118 100644 --- a/crates/omnigraph-server/tests/server.rs +++ b/crates/omnigraph-server/tests/server.rs @@ -675,85 +675,6 @@ async fn healthz_succeeds_after_startup() { } } -#[tokio::test(flavor = "multi_thread")] -async fn request_id_minted_when_absent() { - let (_temp, app) = app_for_loaded_repo().await; - let response = app - .clone() - .oneshot( - Request::builder() - .uri("/healthz") - .method(Method::GET) - .body(Body::empty()) - .unwrap(), - ) - .await - .unwrap(); - let id = response - .headers() - .get("x-request-id") - .expect("X-Request-Id missing") - .to_str() - .unwrap() - .to_owned(); - // ULIDs are 26 chars Crockford base32. - assert_eq!(id.len(), 26); - assert!(id.chars().all(|c| c.is_ascii_alphanumeric())); -} - -#[tokio::test(flavor = "multi_thread")] -async fn request_id_echoed_when_caller_supplies_valid_value() { - let (_temp, app) = app_for_loaded_repo().await; - let response = app - .clone() - .oneshot( - Request::builder() - .uri("/healthz") - .method(Method::GET) - .header("X-Request-Id", "trace-abc123") - .body(Body::empty()) - .unwrap(), - ) - .await - .unwrap(); - assert_eq!( - response - .headers() - .get("x-request-id") - .unwrap() - .to_str() - .unwrap(), - "trace-abc123" - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn request_id_minted_when_caller_supplies_invalid_value() { - let (_temp, app) = app_for_loaded_repo().await; - // 200-char string is a valid HeaderValue but exceeds the inbound length cap. - let too_long = "a".repeat(200); - let response = app - .clone() - .oneshot( - Request::builder() - .uri("/healthz") - .method(Method::GET) - .header("X-Request-Id", &too_long) - .body(Body::empty()) - .unwrap(), - ) - .await - .unwrap(); - let id = response - .headers() - .get("x-request-id") - .unwrap() - .to_str() - .unwrap(); - assert_ne!(id, too_long); - assert_eq!(id.len(), 26); -} - #[tokio::test(flavor = "multi_thread")] async fn schema_drift_returns_conflict_for_snapshot_read_and_change() { let (temp, app) = app_for_loaded_repo().await;