htmx_headers/
lib.rs

1#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2struct AsciiHeaderValue(::http::HeaderValue);
3impl AsciiHeaderValue {
4    fn from_value(value: ::http::HeaderValue) -> Option<Self> {
5        // SAFETY check for [`AsciiHeaderValue::as_str`]
6        let bytes = value.as_bytes();
7        for &b in bytes {
8            if !(b >= 32 && b < 127 || b == b'\t') {
9                return None;
10            }
11        }
12        Some(AsciiHeaderValue(value))
13    }
14
15    fn from_str(s: &str) -> Option<Self> {
16        ::http::HeaderValue::from_str(s).ok().map(AsciiHeaderValue)
17    }
18
19    fn as_str(&self) -> &str {
20        let bytes = self.0.as_bytes();
21        // SAFETY checked in [`AsciiHeaderValue::new`]
22        unsafe { ::std::str::from_utf8_unchecked(bytes) }
23    }
24
25    fn into_value(self) -> ::http::HeaderValue {
26        self.0
27    }
28}
29
30macro_rules! str_header {
31    ($name:ident, $n:ident = $s:expr) => {
32        #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
33        pub struct $name(crate::AsciiHeaderValue);
34        pub static $n: ::headers::HeaderName = ::headers::HeaderName::from_static($s);
35        impl $name {
36            pub fn as_str(&self) -> &str {
37                self.0.as_str()
38            }
39            pub fn into_value(self) -> ::http::HeaderValue {
40                self.0.into_value()
41            }
42            pub fn from_str(s: &str) -> Option<Self> {
43                crate::AsciiHeaderValue::from_str(s).map($name)
44            }
45        }
46        impl ::headers::Header for $name {
47            fn name() -> &'static http::HeaderName {
48                &$n
49            }
50            fn decode<'i, I>(values: &mut I) -> Result<Self, ::headers::Error>
51            where
52                Self: Sized,
53                I: Iterator<Item = &'i ::http::HeaderValue>,
54            {
55                values
56                    .next()
57                    .and_then(|one| values.next().is_none().then_some(one))
58                    .cloned() // Type is Arc-like so cheap to clone
59                    .and_then(crate::AsciiHeaderValue::from_value)
60                    .map($name)
61                    .ok_or_else(::headers::Error::invalid)
62            }
63
64            fn encode<E: Extend<::http::HeaderValue>>(&self, values: &mut E) {
65                values.extend(std::iter::once(self.clone().into_value()))
66            }
67        }
68    };
69}
70
71macro_rules! true_header {
72    ($name:ident, $n:ident = $s:expr) => {
73        #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
74        pub struct $name;
75        pub static $n: ::headers::HeaderName = ::headers::HeaderName::from_static($s);
76        impl $name {
77            pub fn into_value(self) -> ::http::HeaderValue {
78                ::http::HeaderValue::from_static("true")
79            }
80        }
81        impl ::headers::Header for $name {
82            fn name() -> &'static http::HeaderName {
83                &$n
84            }
85            fn decode<'i, I>(values: &mut I) -> Result<Self, ::headers::Error>
86            where
87                Self: Sized,
88                I: Iterator<Item = &'i ::http::HeaderValue>,
89            {
90                values
91                    .next()
92                    .and_then(|one| values.next().is_none().then_some(one))
93                    .filter(|value| value.as_bytes() == b"true")
94                    .is_some()
95                    .then_some($name)
96                    .ok_or_else(::headers::Error::invalid)
97            }
98
99            fn encode<E: Extend<::http::HeaderValue>>(&self, values: &mut E) {
100                values.extend(std::iter::once(self.clone().into_value()))
101            }
102        }
103    };
104}
105
106pub mod request {
107    true_header!(HxBoosted, HX_BOOSTED = "hx-boosted");
108    str_header!(HxCurrentUrl, HX_CURRENT_URL = "hx-current-url");
109    true_header!(
110        HxHistoryRestoreRequest,
111        HX_HISTORY_RESTORE_REQUEST = "hx-history-restore-request"
112    );
113    str_header!(HxPrompt, HX_PROMPT = "hx-prompt");
114    true_header!(HxRequest, HX_REQUEST = "hx-request");
115    str_header!(HxTarget, HX_TARGET = "hx-target");
116    str_header!(HxTriggerName, HX_TRIGGER_NAME = "hx-trigger-name");
117    str_header!(HxTrigger, HX_TRIGGER = "hx-trigger");
118}
119
120pub mod response {
121    str_header!(HxLocation, HX_LOCATION = "hx-location");
122    str_header!(HxPushUrl, HX_PUSH_URL = "hx-push-url");
123    str_header!(HxRedirect, HX_REDIRECT = "hx-redirect");
124    true_header!(HxRefresh, HX_REFRESH = "hx-refresh");
125    str_header!(HxReplaceUrl, HX_REPLACE_URL = "hx-replace-url");
126    str_header!(HxReswap, HX_RESWAP = "hx-reswap");
127    str_header!(HxRetarget, HX_RETARGET = "hx-retarget");
128    str_header!(HxReselect, HX_RESELECT = "hx-reselect");
129    str_header!(HxTrigger, HX_TRIGGER = "hx-trigger");
130    str_header!(
131        HxTriggerAfterSettle,
132        HX_TRIGGER_AFTER_SETTLE = "hx-trigger-after-settle"
133    );
134    str_header!(
135        HxTriggerAfterSwap,
136        HX_TRIGGER_AFTER_SWAP = "hx-trigger-after-swap"
137    );
138}