Skip to main content

htmxtools/request/
hx_current_url.rs

1use std::{
2    iter::once,
3    ops::{Deref, DerefMut},
4};
5
6#[cfg(feature = "axum")]
7use axum_core::extract::{FromRequestParts, OptionalFromRequestParts};
8#[cfg(feature = "axum")]
9use axum_extra::TypedHeader;
10use headers_core::{Error, Header, HeaderName};
11#[cfg(feature = "axum")]
12use http::request::Parts;
13use http::{HeaderValue, Uri};
14
15#[cfg(feature = "auto-vary")]
16use crate::auto_vary::{AutoVaryNotify, HxRequestHeader};
17use crate::util::{iter::IterExt, uri::UriExt};
18
19static HX_CURRENT_URL: HeaderName = HeaderName::from_static("hx-current-url");
20
21/// The current URL of the browser.
22#[derive(Debug, Clone, PartialEq, Eq, Hash)]
23pub struct HxCurrentUrl(Uri);
24
25impl HxCurrentUrl {
26    /// Returns the current URL of the browser.
27    pub fn as_uri(&self) -> &Uri {
28        &self.0
29    }
30}
31
32impl Deref for HxCurrentUrl {
33    type Target = Uri;
34
35    fn deref(&self) -> &Self::Target {
36        &self.0
37    }
38}
39
40impl DerefMut for HxCurrentUrl {
41    fn deref_mut(&mut self) -> &mut Self::Target {
42        &mut self.0
43    }
44}
45
46impl From<Uri> for HxCurrentUrl {
47    fn from(uri: Uri) -> Self {
48        Self(uri)
49    }
50}
51
52impl From<HxCurrentUrl> for Uri {
53    fn from(hx_current_url: HxCurrentUrl) -> Self {
54        hx_current_url.0
55    }
56}
57
58#[cfg(feature = "axum")]
59#[cfg_attr(docsrs, doc(cfg(feature = "axum")))]
60impl<S> FromRequestParts<S> for HxCurrentUrl
61where
62    S: Send + Sync,
63{
64    type Rejection = <TypedHeader<Self> as FromRequestParts<S>>::Rejection;
65
66    async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
67        #[cfg(feature = "auto-vary")]
68        parts.auto_vary_notify(HxRequestHeader::CurrentUrl).await;
69
70        <TypedHeader<Self> as FromRequestParts<S>>::from_request_parts(parts, state)
71            .await
72            .map(|header| header.0)
73    }
74}
75
76#[cfg(feature = "axum")]
77#[cfg_attr(docsrs, doc(cfg(feature = "axum")))]
78impl<S> OptionalFromRequestParts<S> for HxCurrentUrl
79where
80    S: Send + Sync,
81{
82    type Rejection = <TypedHeader<Self> as OptionalFromRequestParts<S>>::Rejection;
83
84    async fn from_request_parts(
85        parts: &mut Parts,
86        state: &S,
87    ) -> Result<Option<Self>, Self::Rejection> {
88        #[cfg(feature = "auto-vary")]
89        parts.auto_vary_notify(HxRequestHeader::CurrentUrl).await;
90
91        <TypedHeader<Self> as OptionalFromRequestParts<S>>::from_request_parts(parts, state)
92            .await
93            .map(|optional_header| optional_header.map(|header| header.0))
94    }
95}
96
97impl Header for HxCurrentUrl {
98    fn name() -> &'static HeaderName {
99        &HX_CURRENT_URL
100    }
101
102    fn decode<'i, I>(values: &mut I) -> Result<Self, Error>
103    where
104        Self: Sized,
105        I: Iterator<Item = &'i HeaderValue>,
106    {
107        values
108            .just_one()
109            .ok_or_else(Error::invalid)?
110            .to_uri()
111            .map(Self)
112    }
113
114    fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
115        let value = HeaderValue::from_uri(&self.0).expect("invalid value for HX-Current-URL");
116        values.extend(once(value));
117    }
118}