Skip to main content

cratestack_axum/transport/
validate.rs

1use axum::http::HeaderMap;
2use cratestack_core::{CratestackError, RouteTransportCapabilities};
3use serde::Deserialize;
4
5use super::http_transport::HttpTransport;
6use super::media_type::{
7    request_content_type, validate_transport_accept_header, validate_transport_content_type_header,
8};
9
10pub fn validate_transport_request_headers<T>(
11    transport: &T,
12    headers: &HeaderMap,
13) -> Result<(), CratestackError>
14where
15    T: HttpTransport,
16{
17    validate_transport_request_headers_for(
18        transport,
19        headers,
20        &RouteTransportCapabilities {
21            request_types: &[],
22            response_types: &[],
23            default_response_type: "",
24            supports_sequence_response: false,
25        },
26    )
27}
28
29pub fn validate_transport_response_headers<T>(
30    transport: &T,
31    headers: &HeaderMap,
32) -> Result<(), CratestackError>
33where
34    T: HttpTransport,
35{
36    validate_transport_response_headers_for(
37        transport,
38        headers,
39        &RouteTransportCapabilities {
40            request_types: &[],
41            response_types: &[],
42            default_response_type: "",
43            supports_sequence_response: false,
44        },
45    )
46}
47
48pub fn validate_transport_request_headers_for<T>(
49    transport: &T,
50    headers: &HeaderMap,
51    capabilities: &RouteTransportCapabilities,
52) -> Result<(), CratestackError>
53where
54    T: HttpTransport,
55{
56    validate_transport_accept_header(headers, &encodable_response_types(transport, capabilities))?;
57    if capabilities.request_types.is_empty() {
58        Ok(())
59    } else {
60        validate_transport_content_type_header(headers, capabilities.request_types)
61    }
62}
63
64pub fn validate_transport_response_headers_for<T>(
65    transport: &T,
66    headers: &HeaderMap,
67    capabilities: &RouteTransportCapabilities,
68) -> Result<(), CratestackError>
69where
70    T: HttpTransport,
71{
72    validate_transport_accept_header(headers, &encodable_response_types(transport, capabilities))
73}
74
75/// `capabilities.response_types` filtered down to what `transport` can
76/// actually encode (cratestack#489): the `Accept` preflight below must
77/// reject a request naming only content types the router will fail to
78/// produce *before* any handler side effect (e.g. a model `create`'s DB
79/// write) runs — not just eventually, once the response encoder in
80/// `encode_transport_result_with_status_for` catches it too late for a
81/// mutation that has already happened. Empty `response_types` (the
82/// capability-free `validate_transport_request_headers`/
83/// `validate_transport_response_headers` wrappers below, unused in this
84/// workspace) passes through unfiltered — `validate_transport_accept_header`
85/// already treats an empty list as "reject anything with an explicit
86/// `Accept`", unchanged from before this fix.
87fn encodable_response_types<T>(
88    transport: &T,
89    capabilities: &RouteTransportCapabilities,
90) -> Vec<&'static str>
91where
92    T: HttpTransport,
93{
94    capabilities
95        .response_types
96        .iter()
97        .copied()
98        .filter(|content_type| transport.can_encode(content_type))
99        .collect()
100}
101
102pub fn decode_transport_request_for<TTransport, TValue>(
103    transport: &TTransport,
104    headers: &HeaderMap,
105    capabilities: &RouteTransportCapabilities,
106    body: &[u8],
107) -> Result<TValue, CratestackError>
108where
109    TTransport: HttpTransport,
110    TValue: for<'de> Deserialize<'de>,
111{
112    let content_type = request_content_type(headers, capabilities.request_types)?;
113    transport.decode_request(content_type, body)
114}
115
116#[cfg(test)]
117mod tests;