1use std::collections::HashMap;
4
5use headers_core::{Header, HeaderValue};
6use http::{HeaderName, Uri};
7use serde::{Deserialize, Serialize};
8
9use super::{convert_header, define_header, string_header, true_header};
10use crate::Swap;
11
12#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
14pub struct AjaxContext {
15 #[serde(skip_serializing_if = "Option::is_none")]
17 pub source: Option<String>,
18
19 #[serde(skip_serializing_if = "Option::is_none")]
21 pub event: Option<String>,
22
23 #[serde(skip_serializing_if = "Option::is_none")]
25 pub handler: Option<String>,
26
27 #[serde(skip_serializing_if = "Option::is_none")]
29 pub target: Option<String>,
30
31 #[serde(skip_serializing_if = "Option::is_none")]
33 pub swap: Option<String>,
34
35 #[serde(skip_serializing_if = "Option::is_none")]
37 pub values: Option<HashMap<String, String>>,
38
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub headers: Option<HashMap<String, String>>,
42
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub select: Option<String>,
46}
47
48define_header! {
49 (HX_LOCATION, "hx-location")
53
54
55 #[derive(Serialize, Deserialize)]
56 pub struct HxLocation {
57 #[serde(with = "http_serde::uri")]
59 pub path: Uri,
60
61 #[serde(flatten)]
63 pub context: Option<AjaxContext>,
64 }
65}
66
67impl Header for HxLocation {
68 fn name() -> &'static HeaderName {
69 &HX_LOCATION
70 }
71
72 fn decode<'i, I>(values: &mut I) -> Result<Self, headers_core::Error>
73 where
74 Self: Sized,
75 I: Iterator<Item = &'i HeaderValue>,
76 {
77 match (values.next(), values.next()) {
78 (Some(value), None) => {
79 serde_json::from_slice(value.as_bytes()).map_err(|_| headers_core::Error::invalid())
80 }
81 _ => Err(headers_core::Error::invalid()),
82 }
83 }
84
85 fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
87 let header = match self {
88 Self {
89 path,
90 context: None,
91 } => HeaderValue::from_str(&path.to_string()).unwrap(),
92 Self {
93 context: Some(_), ..
94 } => {
95 let s = serde_json::to_string(self).unwrap();
96 HeaderValue::from_str(&s).unwrap()
97 }
98 };
99
100 values.extend(std::iter::once(header));
101 }
102}
103
104#[derive(Debug, Clone, PartialEq, Eq)]
106pub enum HxModifyHistory<M: HistoryModification> {
107 Uri(Uri),
109
110 NoChange,
112 #[doc(hidden)]
113 #[allow(dead_code)]
114 Phantom(std::marker::PhantomData<M>),
115}
116
117pub trait HistoryModification {
119 fn name() -> &'static HeaderName;
121}
122
123define_header! {
124 (HX_PUSH_URL, "hx-push-url")
128
129 #[derive(Copy)]
130 pub struct HxPushUrl;
131}
132
133impl HistoryModification for HxPushUrl {
134 fn name() -> &'static HeaderName {
135 &HX_PUSH_URL
136 }
137}
138
139define_header! {
140 (HX_REPLACE_URL, "hx-replace-url")
144
145 #[derive(Copy)]
146 pub struct HxReplaceUrl;
147}
148
149impl HistoryModification for HxReplaceUrl {
150 fn name() -> &'static HeaderName {
151 &HX_REPLACE_URL
152 }
153}
154
155impl<M: HistoryModification> Header for HxModifyHistory<M> {
156 fn name() -> &'static HeaderName {
157 M::name()
158 }
159
160 fn decode<'i, I>(values: &mut I) -> Result<Self, headers_core::Error>
161 where
162 Self: Sized,
163 I: Iterator<Item = &'i HeaderValue>,
164 {
165 match (values.next(), values.next()) {
166 (Some(value), None) => {
167 if value == "false" {
168 Ok(Self::NoChange)
169 } else {
170 value
171 .as_bytes()
172 .try_into()
173 .map(Self::Uri)
174 .map_err(|_| headers_core::Error::invalid())
175 }
176 }
177 _ => Err(headers_core::Error::invalid()),
178 }
179 }
180
181 fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
183 let header = match self {
184 Self::Uri(uri) => HeaderValue::from_str(&uri.to_string()).unwrap(),
185 Self::NoChange => HeaderValue::from_static("false"),
186 Self::Phantom(_) => return,
187 };
188
189 values.extend(std::iter::once(header));
190 }
191}
192
193convert_header! {
194 Uri => (HX_REDIRECT, HxRedirect, "hx-redirect")
196}
197
198true_header! {
199 (HX_REFRESH, HxRefresh, "hx-refresh")
201}
202
203define_header! {
204 (HX_RESWAP, "hx-reswap")
206
207 #[derive(Copy)]
208 pub struct HxReswap(pub Swap);
209}
210
211impl Header for HxReswap {
212 fn name() -> &'static HeaderName {
213 &HX_RESWAP
214 }
215
216 fn decode<'i, I>(values: &mut I) -> Result<Self, headers_core::Error>
217 where
218 Self: Sized,
219 I: Iterator<Item = &'i HeaderValue>,
220 {
221 match (values.next(), values.next()) {
222 (Some(value), None) => value
223 .as_bytes()
224 .try_into()
225 .map(Self)
226 .map_err(|()| headers_core::Error::invalid()),
227 _ => Err(headers_core::Error::invalid()),
228 }
229 }
230
231 fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
233 values.extend(std::iter::once(self.0.into()));
234 }
235}
236
237string_header! {
238 (HX_RETARGET, HxRetarget, "hx-retarget")
240}
241
242string_header! {
243 (HX_RESELECT, HxReselect, "hx-reselect")
245}
246
247define_header! {
248 (HX_TRIGGER, "hx-trigger")
252
253 pub enum HxTrigger<After: TriggerAfter = ()> {
254 List(Vec<String>),
256
257 WithDetails(HashMap<String, serde_json::Value>),
259 #[doc(hidden)]
260 #[allow(dead_code)]
261 Phantom(std::marker::PhantomData<After>),
262 }
263}
264
265pub trait TriggerAfter {
267 fn name() -> &'static HeaderName;
269}
270
271impl TriggerAfter for () {
272 fn name() -> &'static HeaderName {
273 &HX_TRIGGER
274 }
275}
276
277define_header! {
278 (HX_TRIGGER_AFTER_SETTLE, "hx-trigger-after-settle")
282
283 #[derive(Copy)]
284 pub struct AfterSettle;
285}
286
287impl TriggerAfter for AfterSettle {
288 fn name() -> &'static HeaderName {
289 &HX_TRIGGER_AFTER_SETTLE
290 }
291}
292
293define_header! {
294 (HX_TRIGGER_AFTER_SWAP, "hx-trigger-after-swap")
298
299 #[derive(Copy)]
300 pub struct AfterSwap;
301}
302
303impl TriggerAfter for AfterSwap {
304 fn name() -> &'static HeaderName {
305 &HX_TRIGGER_AFTER_SWAP
306 }
307}
308
309impl<After: TriggerAfter> Header for HxTrigger<After> {
310 fn name() -> &'static HeaderName {
311 After::name()
312 }
313
314 fn decode<'i, I>(values: &mut I) -> Result<Self, headers_core::Error>
315 where
316 Self: Sized,
317 I: Iterator<Item = &'i HeaderValue>,
318 {
319 match (values.next(), values.next()) {
320 (Some(value), None) => {
321 let bytes = value.as_bytes();
322 serde_json::from_slice(bytes)
323 .map(Self::WithDetails)
324 .or_else(|_| {
325 let items = value
326 .to_str()
327 .map_err(|_| headers_core::Error::invalid())?
328 .split(',')
329 .map(|s| s.trim().to_owned())
330 .collect();
331
332 Ok(Self::List(items))
333 })
334 }
335 _ => Err(headers_core::Error::invalid()),
336 }
337 }
338
339 fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
341 let val = match self {
342 Self::List(list) => {
343 let s = list.join(", ");
344 HeaderValue::from_str(&s).unwrap()
345 }
346 Self::WithDetails(details) => {
347 let s = serde_json::to_string(details).unwrap();
348 HeaderValue::from_str(&s).unwrap()
349 }
350 Self::Phantom(_) => return,
351 };
352
353 values.extend(std::iter::once(val));
354 }
355}
356
357#[cfg(test)]
358mod tests {
359 use super::*;
360
361 #[test]
362 fn trigger_works() {
363 let val = HeaderValue::from_static(r#"{"event1":"A message", "event2":"Another message"}"#);
364
365 claims::assert_ok_eq!(
366 HxTrigger::<()>::decode(&mut std::iter::once(&val)),
367 HxTrigger::WithDetails(
368 vec![
369 ("event1".to_owned(), "A message".into()),
370 ("event2".to_owned(), "Another message".into()),
371 ]
372 .into_iter()
373 .collect()
374 )
375 );
376
377 let val = HeaderValue::from_static("event1, event2");
378
379 claims::assert_ok_eq!(
380 HxTrigger::<()>::decode(&mut std::iter::once(&val)),
381 HxTrigger::List(vec!["event1".to_owned(), "event2".to_owned()])
382 );
383 }
384}