axum_htmx/
responders.rs

1//! Axum responses for htmx response headers.
2
3use std::convert::Infallible;
4
5use axum_core::response::{IntoResponseParts, ResponseParts};
6use http::HeaderValue;
7
8use crate::{HxError, headers};
9
10mod location;
11pub use location::*;
12mod trigger;
13pub use trigger::*;
14mod vary;
15pub use vary::*;
16
17const HX_SWAP_INNER_HTML: &str = "innerHTML";
18const HX_SWAP_OUTER_HTML: &str = "outerHTML";
19const HX_SWAP_BEFORE_BEGIN: &str = "beforebegin";
20const HX_SWAP_AFTER_BEGIN: &str = "afterbegin";
21const HX_SWAP_BEFORE_END: &str = "beforeend";
22const HX_SWAP_AFTER_END: &str = "afterend";
23const HX_SWAP_DELETE: &str = "delete";
24const HX_SWAP_NONE: &str = "none";
25
26/// The `HX-Push-Url` header.
27///
28/// Pushes a new url into the history stack.
29///
30/// Will fail if the supplied Uri contains characters that are not visible ASCII
31/// (32-127).
32///
33/// See <https://htmx.org/headers/hx-push-url/> for more information.
34#[derive(Debug, Clone)]
35pub struct HxPushUrl(pub String);
36
37impl IntoResponseParts for HxPushUrl {
38    type Error = HxError;
39
40    fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {
41        res.headers_mut().insert(
42            headers::HX_PUSH_URL,
43            HeaderValue::from_maybe_shared(self.0)?,
44        );
45
46        Ok(res)
47    }
48}
49
50impl<'a> From<&'a str> for HxPushUrl {
51    fn from(value: &'a str) -> Self {
52        Self(value.to_string())
53    }
54}
55
56/// The `HX-Redirect` header.
57///
58/// Can be used to do a client-side redirect to a new location.
59///
60/// Will fail if the supplied Uri contains characters that are not visible ASCII
61/// (32-127).
62#[derive(Debug, Clone)]
63pub struct HxRedirect(pub String);
64
65impl IntoResponseParts for HxRedirect {
66    type Error = HxError;
67
68    fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {
69        res.headers_mut().insert(
70            headers::HX_REDIRECT,
71            HeaderValue::from_maybe_shared(self.0)?,
72        );
73
74        Ok(res)
75    }
76}
77
78impl<'a> From<&'a str> for HxRedirect {
79    fn from(value: &'a str) -> Self {
80        Self(value.to_string())
81    }
82}
83
84/// The `HX-Refresh`header.
85///
86/// If set to `true` the client-side will do a full refresh of the page.
87///
88/// This responder will never fail.
89#[derive(Debug, Copy, Clone)]
90pub struct HxRefresh(pub bool);
91
92impl From<bool> for HxRefresh {
93    fn from(value: bool) -> Self {
94        Self(value)
95    }
96}
97
98impl IntoResponseParts for HxRefresh {
99    type Error = Infallible;
100
101    fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {
102        res.headers_mut().insert(
103            headers::HX_REFRESH,
104            if self.0 {
105                HeaderValue::from_static("true")
106            } else {
107                HeaderValue::from_static("false")
108            },
109        );
110
111        Ok(res)
112    }
113}
114
115/// The `HX-Replace-Url` header.
116///
117/// Replaces the currelt URL in the location bar.
118///
119/// Will fail if the supplied Uri contains characters that are not visible ASCII
120/// (32-127).
121///
122/// See <https://htmx.org/headers/hx-replace-url/> for more information.
123#[derive(Debug, Clone)]
124pub struct HxReplaceUrl(pub String);
125
126impl IntoResponseParts for HxReplaceUrl {
127    type Error = HxError;
128
129    fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {
130        res.headers_mut().insert(
131            headers::HX_REPLACE_URL,
132            HeaderValue::from_maybe_shared(self.0)?,
133        );
134
135        Ok(res)
136    }
137}
138
139impl<'a> From<&'a str> for HxReplaceUrl {
140    fn from(value: &'a str) -> Self {
141        Self(value.to_string())
142    }
143}
144
145/// The `HX-Reswap` header.
146///
147/// Allows you to specidy how the response will be swapped.
148///
149/// This responder will never fail.
150#[derive(Debug, Copy, Clone)]
151pub struct HxReswap(pub SwapOption);
152
153impl IntoResponseParts for HxReswap {
154    type Error = Infallible;
155
156    fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {
157        res.headers_mut().insert(headers::HX_RESWAP, self.0.into());
158
159        Ok(res)
160    }
161}
162
163impl From<SwapOption> for HxReswap {
164    fn from(value: SwapOption) -> Self {
165        Self(value)
166    }
167}
168
169/// The `HX-Retarget` header.
170///
171/// A CSS selector that updates the target of the content update to a different
172/// element on the page.
173///
174/// Will fail if the supplied String contains characters that are not visible
175/// ASCII (32-127).
176#[derive(Debug, Clone)]
177pub struct HxRetarget(pub String);
178
179impl IntoResponseParts for HxRetarget {
180    type Error = HxError;
181
182    fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {
183        res.headers_mut().insert(
184            headers::HX_RETARGET,
185            HeaderValue::from_maybe_shared(self.0)?,
186        );
187
188        Ok(res)
189    }
190}
191
192impl<T: Into<String>> From<T> for HxRetarget {
193    fn from(value: T) -> Self {
194        Self(value.into())
195    }
196}
197
198/// The `HX-Reselect` header.
199///
200/// A CSS selector that allows you to choose which part of the response is used
201/// to be swapped in. Overrides an existing hx-select on the triggering element.
202///
203/// Will fail if the supplied String contains characters that are not visible
204/// ASCII (32-127).
205#[derive(Debug, Clone)]
206pub struct HxReselect(pub String);
207
208impl IntoResponseParts for HxReselect {
209    type Error = HxError;
210
211    fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {
212        res.headers_mut().insert(
213            headers::HX_RESELECT,
214            HeaderValue::from_maybe_shared(self.0)?,
215        );
216
217        Ok(res)
218    }
219}
220
221impl<T: Into<String>> From<T> for HxReselect {
222    fn from(value: T) -> Self {
223        Self(value.into())
224    }
225}
226
227/// Values of the `hx-swap` attribute.
228// serde::Serialize is implemented in responders/serde.rs
229#[derive(Debug, Copy, Clone)]
230pub enum SwapOption {
231    /// Replace the inner html of the target element.
232    InnerHtml,
233    /// Replace the entire target element with the response.
234    OuterHtml,
235    /// Insert the response before the target element.
236    BeforeBegin,
237    /// Insert the response before the first child of the target element.
238    AfterBegin,
239    /// Insert the response after the last child of the target element
240    BeforeEnd,
241    /// Insert the response after the target element
242    AfterEnd,
243    /// Deletes the target element regardless of the response
244    Delete,
245    /// Does not append content from response (out of band items will still be
246    /// processed).
247    None,
248}
249
250// can be removed  and automatically derived when
251// https://github.com/serde-rs/serde/issues/2485 is implemented
252#[cfg(feature = "serde")]
253impl ::serde::Serialize for SwapOption {
254    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
255    where
256        S: ::serde::Serializer,
257    {
258        const UNIT_NAME: &str = "SwapOption";
259        match self {
260            Self::InnerHtml => serializer.serialize_unit_variant(UNIT_NAME, 0, HX_SWAP_INNER_HTML),
261            Self::OuterHtml => serializer.serialize_unit_variant(UNIT_NAME, 1, HX_SWAP_OUTER_HTML),
262            Self::BeforeBegin => {
263                serializer.serialize_unit_variant(UNIT_NAME, 2, HX_SWAP_BEFORE_BEGIN)
264            }
265            Self::AfterBegin => {
266                serializer.serialize_unit_variant(UNIT_NAME, 3, HX_SWAP_AFTER_BEGIN)
267            }
268            Self::BeforeEnd => serializer.serialize_unit_variant(UNIT_NAME, 4, HX_SWAP_BEFORE_END),
269            Self::AfterEnd => serializer.serialize_unit_variant(UNIT_NAME, 5, HX_SWAP_AFTER_END),
270            Self::Delete => serializer.serialize_unit_variant(UNIT_NAME, 6, HX_SWAP_DELETE),
271            Self::None => serializer.serialize_unit_variant(UNIT_NAME, 7, HX_SWAP_NONE),
272        }
273    }
274}
275
276impl From<SwapOption> for HeaderValue {
277    fn from(value: SwapOption) -> Self {
278        match value {
279            SwapOption::InnerHtml => HeaderValue::from_static(HX_SWAP_INNER_HTML),
280            SwapOption::OuterHtml => HeaderValue::from_static(HX_SWAP_OUTER_HTML),
281            SwapOption::BeforeBegin => HeaderValue::from_static(HX_SWAP_BEFORE_BEGIN),
282            SwapOption::AfterBegin => HeaderValue::from_static(HX_SWAP_AFTER_BEGIN),
283            SwapOption::BeforeEnd => HeaderValue::from_static(HX_SWAP_BEFORE_END),
284            SwapOption::AfterEnd => HeaderValue::from_static(HX_SWAP_AFTER_END),
285            SwapOption::Delete => HeaderValue::from_static(HX_SWAP_DELETE),
286            SwapOption::None => HeaderValue::from_static(HX_SWAP_NONE),
287        }
288    }
289}