1use http::HeaderValue;
2use http::response::Parts;
3use topcoat_core::{context::Cx, error::Result};
4use topcoat_router::IntoResponseParts;
5
6use crate::SwapOption;
7use crate::header;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct HxPushUrl(pub String);
16
17impl HxPushUrl {
18 #[must_use]
20 pub fn prevent() -> Self {
21 Self("false".to_owned())
22 }
23}
24
25impl<T: Into<String>> From<T> for HxPushUrl {
26 fn from(url: T) -> Self {
27 Self(url.into())
28 }
29}
30
31impl IntoResponseParts for HxPushUrl {
32 fn into_response_parts(self, _cx: &Cx, parts: &mut Parts) -> Result<()> {
33 parts
34 .headers
35 .insert(header::HX_PUSH_URL, HeaderValue::from_str(&self.0)?);
36 Ok(())
37 }
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct HxReplaceUrl(pub String);
47
48impl HxReplaceUrl {
49 #[must_use]
51 pub fn prevent() -> Self {
52 Self("false".to_owned())
53 }
54}
55
56impl<T: Into<String>> From<T> for HxReplaceUrl {
57 fn from(url: T) -> Self {
58 Self(url.into())
59 }
60}
61
62impl IntoResponseParts for HxReplaceUrl {
63 fn into_response_parts(self, _cx: &Cx, parts: &mut Parts) -> Result<()> {
64 parts
65 .headers
66 .insert(header::HX_REPLACE_URL, HeaderValue::from_str(&self.0)?);
67 Ok(())
68 }
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
74pub struct HxRedirect(pub String);
75
76impl<T: Into<String>> From<T> for HxRedirect {
77 fn from(url: T) -> Self {
78 Self(url.into())
79 }
80}
81
82impl IntoResponseParts for HxRedirect {
83 fn into_response_parts(self, _cx: &Cx, parts: &mut Parts) -> Result<()> {
84 parts
85 .headers
86 .insert(header::HX_REDIRECT, HeaderValue::from_str(&self.0)?);
87 Ok(())
88 }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub struct HxRefresh(pub bool);
95
96impl From<bool> for HxRefresh {
97 fn from(refresh: bool) -> Self {
98 Self(refresh)
99 }
100}
101
102impl IntoResponseParts for HxRefresh {
103 fn into_response_parts(self, _cx: &Cx, parts: &mut Parts) -> Result<()> {
104 let value = if self.0 { "true" } else { "false" };
105 parts
106 .headers
107 .insert(header::HX_REFRESH, HeaderValue::from_static(value));
108 Ok(())
109 }
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub struct HxReswap(pub SwapOption);
116
117impl From<SwapOption> for HxReswap {
118 fn from(option: SwapOption) -> Self {
119 Self(option)
120 }
121}
122
123impl IntoResponseParts for HxReswap {
124 fn into_response_parts(self, _cx: &Cx, parts: &mut Parts) -> Result<()> {
125 parts.headers.insert(header::HX_RESWAP, self.0.into());
126 Ok(())
127 }
128}
129
130#[derive(Debug, Clone, PartialEq, Eq)]
133pub struct HxRetarget(pub String);
134
135impl<T: Into<String>> From<T> for HxRetarget {
136 fn from(selector: T) -> Self {
137 Self(selector.into())
138 }
139}
140
141impl IntoResponseParts for HxRetarget {
142 fn into_response_parts(self, _cx: &Cx, parts: &mut Parts) -> Result<()> {
143 parts
144 .headers
145 .insert(header::HX_RETARGET, HeaderValue::from_str(&self.0)?);
146 Ok(())
147 }
148}
149
150#[derive(Debug, Clone, PartialEq, Eq)]
153pub struct HxReselect(pub String);
154
155impl<T: Into<String>> From<T> for HxReselect {
156 fn from(selector: T) -> Self {
157 Self(selector.into())
158 }
159}
160
161impl IntoResponseParts for HxReselect {
162 fn into_response_parts(self, _cx: &Cx, parts: &mut Parts) -> Result<()> {
163 parts
164 .headers
165 .insert(header::HX_RESELECT, HeaderValue::from_str(&self.0)?);
166 Ok(())
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 fn parts() -> Parts {
175 http::Response::new(()).into_parts().0
176 }
177
178 fn header_value(parts: &Parts, name: &http::HeaderName) -> String {
179 parts
180 .headers
181 .get(name)
182 .unwrap()
183 .to_str()
184 .unwrap()
185 .to_owned()
186 }
187
188 #[test]
189 fn push_url_sets_header() {
190 let mut parts = parts();
191 HxPushUrl::from("/new")
192 .into_response_parts(&Cx::default(), &mut parts)
193 .unwrap();
194 assert_eq!(header_value(&parts, &header::HX_PUSH_URL), "/new");
195 }
196
197 #[test]
198 fn push_url_prevent_is_false() {
199 let mut parts = parts();
200 HxPushUrl::prevent()
201 .into_response_parts(&Cx::default(), &mut parts)
202 .unwrap();
203 assert_eq!(header_value(&parts, &header::HX_PUSH_URL), "false");
204 }
205
206 #[test]
207 fn refresh_serializes_bool() {
208 let mut parts = parts();
209 HxRefresh(true)
210 .into_response_parts(&Cx::default(), &mut parts)
211 .unwrap();
212 assert_eq!(header_value(&parts, &header::HX_REFRESH), "true");
213 }
214
215 #[test]
216 fn reswap_uses_swap_option_string() {
217 let mut parts = parts();
218 HxReswap(SwapOption::BeforeEnd)
219 .into_response_parts(&Cx::default(), &mut parts)
220 .unwrap();
221 assert_eq!(header_value(&parts, &header::HX_RESWAP), "beforeend");
222 }
223
224 #[test]
225 fn retarget_and_reselect_carry_selectors() {
226 let mut parts = parts();
227 HxRetarget::from("#main")
228 .into_response_parts(&Cx::default(), &mut parts)
229 .unwrap();
230 HxReselect::from(".item")
231 .into_response_parts(&Cx::default(), &mut parts)
232 .unwrap();
233 assert_eq!(header_value(&parts, &header::HX_RETARGET), "#main");
234 assert_eq!(header_value(&parts, &header::HX_RESELECT), ".item");
235 }
236}