axum_htmx/responders/
vary.rs

1use axum_core::response::{IntoResponseParts, ResponseParts};
2use http::header::{HeaderValue, VARY};
3
4use crate::{HxError, extractors, headers};
5
6const HX_REQUEST: HeaderValue = HeaderValue::from_static(headers::HX_REQUEST_STR);
7const HX_TARGET: HeaderValue = HeaderValue::from_static(headers::HX_TARGET_STR);
8const HX_TRIGGER: HeaderValue = HeaderValue::from_static(headers::HX_TRIGGER_STR);
9const HX_TRIGGER_NAME: HeaderValue = HeaderValue::from_static(headers::HX_TRIGGER_NAME_STR);
10
11/// The `Vary: HX-Request` header.
12///
13/// You may want to add this header to the response if your handler responds
14/// differently based on the `HX-Request` request header.
15///
16/// For example, if your server renders the full HTML when the `HX-Request`
17/// header is missing or `false`, and it renders a fragment of that HTML when
18/// `HX-Request: true`.
19///
20/// You probably need this only for `GET` requests, as other HTTP methods are
21/// not cached by default.
22///
23/// See <https://htmx.org/docs/#caching> for more information.
24#[derive(Debug, Clone)]
25pub struct VaryHxRequest;
26
27impl IntoResponseParts for VaryHxRequest {
28    type Error = HxError;
29
30    fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {
31        res.headers_mut().try_append(VARY, HX_REQUEST)?;
32
33        Ok(res)
34    }
35}
36
37impl extractors::HxRequest {
38    /// Convenience method to create the corresponding `Vary` response header
39    pub fn vary_response() -> VaryHxRequest {
40        VaryHxRequest
41    }
42}
43
44/// The `Vary: HX-Target` header.
45///
46/// You may want to add this header to the response if your handler responds
47/// differently based on the `HX-Target` request header.
48///
49/// You probably need this only for `GET` requests, as other HTTP methods are
50/// not cached by default.
51///
52/// See <https://htmx.org/docs/#caching> for more information.
53#[derive(Debug, Clone)]
54pub struct VaryHxTarget;
55
56impl IntoResponseParts for VaryHxTarget {
57    type Error = HxError;
58
59    fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {
60        res.headers_mut().try_append(VARY, HX_TARGET)?;
61
62        Ok(res)
63    }
64}
65
66impl extractors::HxTarget {
67    /// Convenience method to create the corresponding `Vary` response header
68    pub fn vary_response() -> VaryHxTarget {
69        VaryHxTarget
70    }
71}
72
73/// The `Vary: HX-Trigger` header.
74///
75/// You may want to add this header to the response if your handler responds
76/// differently based on the `HX-Trigger` request header.
77///
78/// You probably need this only for `GET` requests, as other HTTP methods are
79/// not cached by default.
80///
81/// See <https://htmx.org/docs/#caching> for more information.
82#[derive(Debug, Clone)]
83pub struct VaryHxTrigger;
84
85impl IntoResponseParts for VaryHxTrigger {
86    type Error = HxError;
87
88    fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {
89        res.headers_mut().try_append(VARY, HX_TRIGGER)?;
90
91        Ok(res)
92    }
93}
94
95impl extractors::HxTrigger {
96    /// Convenience method to create the corresponding `Vary` response header
97    pub fn vary_response() -> VaryHxTrigger {
98        VaryHxTrigger
99    }
100}
101
102/// The `Vary: HX-Trigger-Name` header.
103///
104/// You may want to add this header to the response if your handler responds
105/// differently based on the `HX-Trigger-Name` request header.
106///
107/// You probably need this only for `GET` requests, as other HTTP methods are
108/// not cached by default.
109///
110/// See <https://htmx.org/docs/#caching> for more information.
111#[derive(Debug, Clone)]
112pub struct VaryHxTriggerName;
113
114impl IntoResponseParts for VaryHxTriggerName {
115    type Error = HxError;
116
117    fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {
118        res.headers_mut().try_append(VARY, HX_TRIGGER_NAME)?;
119
120        Ok(res)
121    }
122}
123
124impl extractors::HxTriggerName {
125    /// Convenience method to create the corresponding `Vary` response header
126    pub fn vary_response() -> VaryHxTriggerName {
127        VaryHxTriggerName
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use std::collections::hash_set::HashSet;
134
135    use axum::{Router, routing::get};
136
137    use super::*;
138
139    #[tokio::test]
140    async fn multiple_headers() {
141        let app = Router::new().route("/", get(|| async { (VaryHxRequest, VaryHxTarget, "foo") }));
142        let server = axum_test::TestServer::new(app).unwrap();
143
144        let resp = server.get("/").await;
145        let values: HashSet<HeaderValue> = resp.iter_headers_by_name("vary").cloned().collect();
146        assert_eq!(values, HashSet::from([HX_REQUEST, HX_TARGET]));
147    }
148}