vti_common/trust_task/extractor.rs
1//! Axum extractor + middleware function for the `Trust-Task` header.
2//!
3//! Two surfaces:
4//!
5//! - [`TrustTaskHeader`] — extractor for handlers that want to **read**
6//! the validated task. Useful for diagnostic endpoints; routes that
7//! only need to **enforce** the task should use
8//! [`super::router::TrustTaskRouter`] instead.
9//! - [`validate_header`] — the middleware function the router builder
10//! layers onto each route. Public-crate (`pub(crate)`) so the router
11//! can compose it; not part of the user-facing API.
12
13use axum::extract::{FromRequestParts, Request};
14use axum::http::request::Parts;
15use axum::middleware::Next;
16use axum::response::Response;
17
18use super::{HEADER_NAME, TrustTask};
19use crate::error::AppError;
20
21/// Axum extractor: pulls the `Trust-Task` header off a request and
22/// parses it into a validated [`TrustTask`].
23///
24/// Rejections (per spec §16.2):
25/// - missing header → [`AppError::TrustTaskMissing`] (400)
26/// - malformed value → [`AppError::TrustTaskMalformed`] (400)
27/// - non-UTF-8 header → [`AppError::Validation`] (400)
28///
29/// **Note**: using this extractor *does not* enforce exact-match against
30/// a handler's expected task. That correctness check lives in
31/// [`super::router::TrustTaskRouter::route_with_task`]. Use this
32/// extractor only when a handler genuinely needs to read the task
33/// (e.g. for logging or for forwarding it to another service).
34pub struct TrustTaskHeader(pub TrustTask);
35
36impl<S> FromRequestParts<S> for TrustTaskHeader
37where
38 S: Send + Sync,
39{
40 type Rejection = AppError;
41
42 async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
43 let raw = parts
44 .headers
45 .get(HEADER_NAME)
46 .ok_or(AppError::TrustTaskMissing)?;
47 let s = raw
48 .to_str()
49 .map_err(|_| AppError::Validation("Trust-Task header is not valid UTF-8".into()))?;
50 let task = TrustTask::new(s)?;
51 Ok(Self(task))
52 }
53}
54
55/// Middleware function: enforce exact-match of the request's
56/// `Trust-Task` header against `expected`.
57///
58/// Wired in by [`super::router::TrustTaskRouter::route_with_task`].
59/// Returns the structured `AppError::TrustTaskMismatch` on mismatch
60/// (which renders to 415 with a JSON body naming the expected task)
61/// and `AppError::TrustTaskMissing` if the header is absent.
62pub(crate) async fn validate_header(
63 expected: &TrustTask,
64 request: Request,
65 next: Next,
66) -> Result<Response, AppError> {
67 let raw = request
68 .headers()
69 .get(HEADER_NAME)
70 .ok_or(AppError::TrustTaskMissing)?;
71 let received = raw
72 .to_str()
73 .map_err(|_| AppError::Validation("Trust-Task header is not valid UTF-8".into()))?;
74
75 if received != expected.as_str() {
76 return Err(AppError::TrustTaskMismatch {
77 expected: expected.as_str().to_string(),
78 received: Some(received.to_string()),
79 });
80 }
81
82 Ok(next.run(request).await)
83}