diff --git a/src/lib.rs b/src/lib.rs index 584fc87..80f9134 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,191 +1,3 @@ #![forbid(unsafe_code)] - -use axum::{extract::FromRequestParts, http::request::Parts}; - -/// Represents all of the headers that can be sent in a request to the server. -/// -/// See for more information. -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum HtmxRequestHeader { - /// Indicates that the request is via an element using `hx-boost` attribute. - /// - /// See for more information. - Boosted, - /// The current URL of the browser. - CurrentUrl, - /// `true` if the request is for history restoration after a miss in the - /// local history cache. - HistoryRestoreRequest, - /// The user response to an `hx-prompt` - /// - /// See for more information. - Prompt, - /// Always `true`. - Request, - /// The `id` of the target element, if it exists. - Target, - /// The `name` of the triggered element, if it exists. - TriggerName, - /// The `id` of the triggered element, if it exists. - Trigger, -} - -impl HtmxRequestHeader { - pub fn as_str(&self) -> &'static str { - match self { - HtmxRequestHeader::Boosted => "HX-Boosted", - HtmxRequestHeader::CurrentUrl => "HX-Current-Url", - HtmxRequestHeader::HistoryRestoreRequest => "HX-History-Restore-Request", - HtmxRequestHeader::Prompt => "HX-Prompt", - HtmxRequestHeader::Request => "HX-Request", - HtmxRequestHeader::Target => "HX-Target", - HtmxRequestHeader::TriggerName => "HX-Trigger-Name", - HtmxRequestHeader::Trigger => "HX-Trigger", - } - } -} - -/// Represents all of the headers that can be sent in a response to the client. -/// -/// See for more information. -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum HtmxResponseHeader { - /// Allows you to do a client-side redirect that does not do a full page - /// reload. - Location, - /// Pushes a new URL onto the history stack. - PushUrl, - /// Can be used to do a client-side redirect to a new location. - Redirect, - /// If set to `true`, the client will do a full refresh on the page. - Refresh, - /// Replaces the currelt URL in the location bar. - ReplaceUrl, - /// Allows you to specify how the response value will be swapped. - /// - /// See for more information. - Reswap, - /// A CSS selector that update the target of the content update to a - /// different element on the page. - Retarget, - /// A CSS selector that allows you to choose which part of the response is - /// used to be swapped in. Overrides an existing `hx-select` on the - /// triggering element - Reselect, - /// Allows you to trigger client-side events. - /// - /// See for more information. - Trigger, - /// Allows you to trigger client-side events. - /// - /// See for more information. - TriggerAfterSettle, - /// Allows you to trigger client-side events. - /// - /// See for more information. - TriggerAfterSwap, -} - -impl HtmxResponseHeader { - pub fn as_str(&self) -> &'static str { - match self { - HtmxResponseHeader::Location => "HX-Location", - HtmxResponseHeader::PushUrl => "HX-Push-Url", - HtmxResponseHeader::Redirect => "HX-Redirect", - HtmxResponseHeader::Refresh => "HX-Refresh", - HtmxResponseHeader::ReplaceUrl => "HX-Replace-Url", - HtmxResponseHeader::Reswap => "HX-Reswap", - HtmxResponseHeader::Retarget => "HX-Retarget", - HtmxResponseHeader::Reselect => "HX-Reselect", - HtmxResponseHeader::Trigger => "HX-Trigger", - HtmxResponseHeader::TriggerAfterSettle => "HX-Trigger-After-Settle", - HtmxResponseHeader::TriggerAfterSwap => "HX-Trigger-After-Swap", - } - } -} - -/// The `HX-Boosted` header. This header is set when a request is made with the -/// "hx-boost" attribute is set on an element. -/// -/// This extractor does not fail if no header is present, instead returning a -/// `false` value. -/// -/// See for more information. -#[derive(Debug, Clone, Copy)] -pub struct HxBoosted(pub bool); - -#[axum::async_trait] -impl FromRequestParts for HxBoosted -where - S: Send + Sync, -{ - type Rejection = std::convert::Infallible; - - async fn from_request_parts(parts: &mut Parts, _: &S) -> Result { - if parts - .headers - .contains_key(HtmxRequestHeader::Boosted.as_str()) - { - return Ok(HxBoosted(true)); - } else { - return Ok(HxBoosted(false)); - } - } -} - -#[derive(Debug, Clone)] -pub struct HxCurrentUrl(pub String); - -#[axum::async_trait] -impl FromRequestParts for HxCurrentUrl -where - S: Send + Sync, -{ - type Rejection = std::convert::Infallible; - - async fn from_request_parts(parts: &mut Parts, _: &S) -> Result { - if let Some(url) = parts.headers.get(HtmxRequestHeader::CurrentUrl.as_str()) { - if let Ok(url) = url.to_str() { - return Ok(HxCurrentUrl(url.to_string())); - } - } - - return Ok(HxCurrentUrl("".to_string())); - } -} - -#[derive(Debug, Clone, Copy)] -pub struct HxRequest(pub bool); - -#[axum::async_trait] -impl FromRequestParts for HxRequest -where - S: Send + Sync, -{ - type Rejection = std::convert::Infallible; - - async fn from_request_parts(_: &mut Parts, _: &S) -> Result { - return Ok(HxRequest(true)); - } -} - -#[derive(Debug, Clone)] -pub struct HxTarget(pub Option); - -#[axum::async_trait] -impl FromRequestParts for HxTarget -where - S: Send + Sync, -{ - type Rejection = std::convert::Infallible; - - async fn from_request_parts(parts: &mut Parts, _: &S) -> Result { - if let Some(target) = parts.headers.get(HtmxRequestHeader::Target.as_str()) { - if let Ok(target) = target.to_str() { - return Ok(HxTarget(Some(target.to_string()))); - } - } - - return Ok(HxTarget(None)); - } -} +pub mod request; +pub mod response; diff --git a/src/request.rs b/src/request.rs new file mode 100644 index 0000000..7c2e167 --- /dev/null +++ b/src/request.rs @@ -0,0 +1,172 @@ +use axum::{extract::FromRequestParts, http::request::Parts}; + +/// Represents all of the headers that can be sent in a request to the server. +/// +/// See for more information. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum HtmxRequestHeader { + /// Indicates that the request is via an element using `hx-boost` attribute. + /// + /// See for more information. + Boosted, + /// The current URL of the browser. + CurrentUrl, + /// `true` if the request is for history restoration after a miss in the + /// local history cache. + HistoryRestoreRequest, + /// The user response to an `hx-prompt` + /// + /// See for more information. + Prompt, + /// Always `true`. + Request, + /// The `id` of the target element, if it exists. + Target, + /// The `name` of the triggered element, if it exists. + TriggerName, + /// The `id` of the triggered element, if it exists. + Trigger, +} + +impl HtmxRequestHeader { + pub fn as_str(&self) -> &'static str { + match self { + HtmxRequestHeader::Boosted => "HX-Boosted", + HtmxRequestHeader::CurrentUrl => "HX-Current-Url", + HtmxRequestHeader::HistoryRestoreRequest => "HX-History-Restore-Request", + HtmxRequestHeader::Prompt => "HX-Prompt", + HtmxRequestHeader::Request => "HX-Request", + HtmxRequestHeader::Target => "HX-Target", + HtmxRequestHeader::TriggerName => "HX-Trigger-Name", + HtmxRequestHeader::Trigger => "HX-Trigger", + } + } +} + +/// The `HX-Boosted` header. This header is set when a request is made with the +/// "hx-boost" attribute is set on an element. +/// +/// This extractor does not fail if no header is present, instead returning a +/// `false` value. +/// +/// See for more information. +#[derive(Debug, Clone, Copy)] +pub struct HxBoosted(pub bool); + +#[axum::async_trait] +impl FromRequestParts for HxBoosted +where + S: Send + Sync, +{ + type Rejection = std::convert::Infallible; + + async fn from_request_parts(parts: &mut Parts, _: &S) -> Result { + if parts + .headers + .contains_key(HtmxRequestHeader::Boosted.as_str()) + { + return Ok(HxBoosted(true)); + } else { + return Ok(HxBoosted(false)); + } + } +} + +#[derive(Debug, Clone)] +pub struct HxCurrentUrl(pub String); + +#[axum::async_trait] +impl FromRequestParts for HxCurrentUrl +where + S: Send + Sync, +{ + type Rejection = std::convert::Infallible; + + async fn from_request_parts(parts: &mut Parts, _: &S) -> Result { + if let Some(url) = parts.headers.get(HtmxRequestHeader::CurrentUrl.as_str()) { + if let Ok(url) = url.to_str() { + return Ok(HxCurrentUrl(url.to_string())); + } + } + + return Ok(HxCurrentUrl("".to_string())); + } +} + +#[derive(Debug, Clone, Copy)] +pub struct HxRequest(pub bool); + +#[axum::async_trait] +impl FromRequestParts for HxRequest +where + S: Send + Sync, +{ + type Rejection = std::convert::Infallible; + + async fn from_request_parts(_: &mut Parts, _: &S) -> Result { + return Ok(HxRequest(true)); + } +} + +#[derive(Debug, Clone)] +pub struct HxTarget(pub Option); + +#[axum::async_trait] +impl FromRequestParts for HxTarget +where + S: Send + Sync, +{ + type Rejection = std::convert::Infallible; + + async fn from_request_parts(parts: &mut Parts, _: &S) -> Result { + if let Some(target) = parts.headers.get(HtmxRequestHeader::Target.as_str()) { + if let Ok(target) = target.to_str() { + return Ok(HxTarget(Some(target.to_string()))); + } + } + + return Ok(HxTarget(None)); + } +} + +#[derive(Debug, Clone)] +pub struct HxTriggerName(pub Option); + +#[axum::async_trait] +impl FromRequestParts for HxTriggerName +where + S: Send + Sync, +{ + type Rejection = std::convert::Infallible; + + async fn from_request_parts(parts: &mut Parts, _: &S) -> Result { + if let Some(trigger_name) = parts.headers.get(HtmxRequestHeader::TriggerName.as_str()) { + if let Ok(trigger_name) = trigger_name.to_str() { + return Ok(HxTriggerName(Some(trigger_name.to_string()))); + } + } + + return Ok(HxTriggerName(None)); + } +} + +#[derive(Debug, Clone)] +pub struct HxTrigger(pub Option); + +#[axum::async_trait] +impl FromRequestParts for HxTrigger +where + S: Send + Sync, +{ + type Rejection = std::convert::Infallible; + + async fn from_request_parts(parts: &mut Parts, _: &S) -> Result { + if let Some(trigger) = parts.headers.get(HtmxRequestHeader::Trigger.as_str()) { + if let Ok(trigger) = trigger.to_str() { + return Ok(HxTrigger(Some(trigger.to_string()))); + } + } + + return Ok(HxTrigger(None)); + } +} diff --git a/src/response.rs b/src/response.rs new file mode 100644 index 0000000..5cf8757 --- /dev/null +++ b/src/response.rs @@ -0,0 +1,58 @@ +/// Represents all of the headers that can be sent in a response to the client. +/// +/// See for more information. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum HtmxResponseHeader { + /// Allows you to do a client-side redirect that does not do a full page + /// reload. + Location, + /// Pushes a new URL onto the history stack. + PushUrl, + /// Can be used to do a client-side redirect to a new location. + Redirect, + /// If set to `true`, the client will do a full refresh on the page. + Refresh, + /// Replaces the currelt URL in the location bar. + ReplaceUrl, + /// Allows you to specify how the response value will be swapped. + /// + /// See for more information. + Reswap, + /// A CSS selector that update the target of the content update to a + /// different element on the page. + Retarget, + /// A CSS selector that allows you to choose which part of the response is + /// used to be swapped in. Overrides an existing `hx-select` on the + /// triggering element + Reselect, + /// Allows you to trigger client-side events. + /// + /// See for more information. + Trigger, + /// Allows you to trigger client-side events. + /// + /// See for more information. + TriggerAfterSettle, + /// Allows you to trigger client-side events. + /// + /// See for more information. + TriggerAfterSwap, +} + +impl HtmxResponseHeader { + pub fn as_str(&self) -> &'static str { + match self { + HtmxResponseHeader::Location => "HX-Location", + HtmxResponseHeader::PushUrl => "HX-Push-Url", + HtmxResponseHeader::Redirect => "HX-Redirect", + HtmxResponseHeader::Refresh => "HX-Refresh", + HtmxResponseHeader::ReplaceUrl => "HX-Replace-Url", + HtmxResponseHeader::Reswap => "HX-Reswap", + HtmxResponseHeader::Retarget => "HX-Retarget", + HtmxResponseHeader::Reselect => "HX-Reselect", + HtmxResponseHeader::Trigger => "HX-Trigger", + HtmxResponseHeader::TriggerAfterSettle => "HX-Trigger-After-Settle", + HtmxResponseHeader::TriggerAfterSwap => "HX-Trigger-After-Swap", + } + } +}