Skip to main content

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}