From d0db3a404202d9b1b0f38d675e4abbf470dd7646 Mon Sep 17 00:00:00 2001 From: imbolc Date: Tue, 30 Apr 2024 10:53:00 +0600 Subject: [PATCH] Vary headers, closes #14 --- README.md | 19 +++++++ src/responders.rs | 2 + src/responders/vary.rs | 123 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 144 insertions(+) create mode 100644 src/responders/vary.rs diff --git a/README.md b/README.md index a176207..ba911b6 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,25 @@ any of your responses. | `HX-Trigger-After-Settle` | `HxResponseTrigger` | `axum_htmx::serde::HxEvent` | | `HX-Trigger-After-Swap` | `HxResponseTrigger` | `axum_htmx::serde::HxEvent` | +Also, there are corresponding cache-related headers, which you may want to add to +`GET` responses, depending on the htmx headers. + +_For example, if your server renders the full HTML when the `HX-Request` header is +missing or `false`, and it renders a fragment of that HTML when `HX-Request: true`, +you need to add `Vary: HX-Request`. That causes the cache to be keyed based on a +composite of the response URL and the `HX-Request` request header - rather than +being based just on the response URL._ + +Refer to [caching htmx docs section](https://htmx.org/docs/#caching) for details. + +| Header | Responder | +|-------------------------|---------------------| +| `Vary: HX-Request` | `VaryHxRequest` | +| `Vary: HX-Target` | `VaryHxTarget` | +| `Vary: HX-Trigger` | `VaryHxTrigger` | +| `Vary: HX-Trigger-Name` | `VaryHxTriggerName` | + + ## Request Guards __Requires features `guards`.__ diff --git a/src/responders.rs b/src/responders.rs index fdc06e4..e7de8d2 100644 --- a/src/responders.rs +++ b/src/responders.rs @@ -11,6 +11,8 @@ mod location; pub use location::*; mod trigger; pub use trigger::*; +mod vary; +pub use vary::*; const HX_SWAP_INNER_HTML: &str = "innerHTML"; const HX_SWAP_OUTER_HTML: &str = "outerHTML"; diff --git a/src/responders/vary.rs b/src/responders/vary.rs new file mode 100644 index 0000000..1ee7312 --- /dev/null +++ b/src/responders/vary.rs @@ -0,0 +1,123 @@ +use axum_core::response::{IntoResponseParts, ResponseParts}; +use http::header::VARY; + +use crate::{extractors, headers, HxError}; + +/// The `Vary: HX-Request` header. +/// +/// You may want to add this header to the response if your handler responds differently based on +/// the `HX-Request` request header. +/// +/// For example, if your server renders the full HTML when the `HX-Request` header is missing or +/// `false`, and it renders a fragment of that HTML when `HX-Request: true`. +/// +/// You probably need this only for `GET` requests, as other HTTP methods are not cached by default. +/// +/// See for more information. +#[derive(Debug, Clone)] +pub struct VaryHxRequest; + +impl IntoResponseParts for VaryHxRequest { + type Error = HxError; + + fn into_response_parts(self, mut res: ResponseParts) -> Result { + res.headers_mut() + .insert(VARY, headers::HX_REQUEST.try_into()?); + + Ok(res) + } +} + +impl extractors::HxRequest { + /// Convenience method to create the corresponding `Vary` response header + pub fn vary_response() -> VaryHxRequest { + VaryHxRequest + } +} + +/// The `Vary: HX-Target` header. +/// +/// You may want to add this header to the response if your handler responds differently based on +/// the `HX-Target` request header. +/// +/// You probably need this only for `GET` requests, as other HTTP methods are not cached by default. +/// +/// See for more information. +#[derive(Debug, Clone)] +pub struct VaryHxTarget; + +impl IntoResponseParts for VaryHxTarget { + type Error = HxError; + + fn into_response_parts(self, mut res: ResponseParts) -> Result { + res.headers_mut() + .insert(VARY, headers::HX_TARGET.try_into()?); + + Ok(res) + } +} + +impl extractors::HxTarget { + /// Convenience method to create the corresponding `Vary` response header + pub fn vary_response() -> VaryHxTarget { + VaryHxTarget + } +} + +/// The `Vary: HX-Trigger` header. +/// +/// You may want to add this header to the response if your handler responds differently based on +/// the `HX-Trigger` request header. +/// +/// You probably need this only for `GET` requests, as other HTTP methods are not cached by default. +/// +/// See for more information. +#[derive(Debug, Clone)] +pub struct VaryHxTrigger; + +impl IntoResponseParts for VaryHxTrigger { + type Error = HxError; + + fn into_response_parts(self, mut res: ResponseParts) -> Result { + res.headers_mut() + .insert(VARY, headers::HX_TRIGGER.try_into()?); + + Ok(res) + } +} + +impl extractors::HxTrigger { + /// Convenience method to create the corresponding `Vary` response header + pub fn vary_response() -> VaryHxTrigger { + VaryHxTrigger + } +} + +/// The `Vary: HX-Trigger-Name` header. +/// +/// You may want to add this header to the response if your handler responds differently based on +/// the `HX-Trigger-Name` request header. +/// +/// You probably need this only for `GET` requests, as other HTTP methods are not cached by default. +/// +/// See for more information. +#[derive(Debug, Clone)] +pub struct VaryHxTriggerName; + +impl IntoResponseParts for VaryHxTriggerName { + type Error = HxError; + + fn into_response_parts(self, mut res: ResponseParts) -> Result { + res.headers_mut() + .insert(VARY, headers::HX_TRIGGER_NAME.try_into()?); + + Ok(res) + } +} + +impl extractors::HxTriggerName { + /// Convenience method to create the corresponding `Vary` response header + pub fn vary_response() -> VaryHxTriggerName { + VaryHxTriggerName + } +}