mirror of
https://github.com/robertwayne/axum-htmx
synced 2024-12-01 15:34:32 +01:00
The remaining extractors
This commit is contained in:
parent
c6f8bcb504
commit
5220c8b861
2 changed files with 117 additions and 21 deletions
|
@ -137,11 +137,10 @@ where
|
||||||
type Rejection = std::convert::Infallible;
|
type Rejection = std::convert::Infallible;
|
||||||
|
|
||||||
async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
|
async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
|
||||||
use crate::vary_middleware::{HxRequestExtracted, Notifier};
|
|
||||||
parts
|
parts
|
||||||
.extensions
|
.extensions
|
||||||
.get_mut::<HxRequestExtracted>()
|
.get_mut::<crate::vary_middleware::HxRequestExtracted>()
|
||||||
.map(Notifier::notify);
|
.map(crate::vary_middleware::Notifier::notify);
|
||||||
|
|
||||||
if parts.headers.contains_key(HX_REQUEST) {
|
if parts.headers.contains_key(HX_REQUEST) {
|
||||||
return Ok(HxRequest(true));
|
return Ok(HxRequest(true));
|
||||||
|
@ -170,11 +169,10 @@ where
|
||||||
type Rejection = std::convert::Infallible;
|
type Rejection = std::convert::Infallible;
|
||||||
|
|
||||||
async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
|
async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
|
||||||
use crate::vary_middleware::{HxTargetExtracted, Notifier};
|
|
||||||
parts
|
parts
|
||||||
.extensions
|
.extensions
|
||||||
.get_mut::<HxTargetExtracted>()
|
.get_mut::<crate::vary_middleware::HxTargetExtracted>()
|
||||||
.map(Notifier::notify);
|
.map(crate::vary_middleware::Notifier::notify);
|
||||||
|
|
||||||
if let Some(target) = parts.headers.get(HX_TARGET) {
|
if let Some(target) = parts.headers.get(HX_TARGET) {
|
||||||
if let Ok(target) = target.to_str() {
|
if let Ok(target) = target.to_str() {
|
||||||
|
@ -205,6 +203,11 @@ where
|
||||||
type Rejection = std::convert::Infallible;
|
type Rejection = std::convert::Infallible;
|
||||||
|
|
||||||
async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
|
async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
|
||||||
|
parts
|
||||||
|
.extensions
|
||||||
|
.get_mut::<crate::vary_middleware::HxTriggerNameExtracted>()
|
||||||
|
.map(crate::vary_middleware::Notifier::notify);
|
||||||
|
|
||||||
if let Some(trigger_name) = parts.headers.get(HX_TRIGGER_NAME) {
|
if let Some(trigger_name) = parts.headers.get(HX_TRIGGER_NAME) {
|
||||||
if let Ok(trigger_name) = trigger_name.to_str() {
|
if let Ok(trigger_name) = trigger_name.to_str() {
|
||||||
return Ok(HxTriggerName(Some(trigger_name.to_string())));
|
return Ok(HxTriggerName(Some(trigger_name.to_string())));
|
||||||
|
@ -234,6 +237,11 @@ where
|
||||||
type Rejection = std::convert::Infallible;
|
type Rejection = std::convert::Infallible;
|
||||||
|
|
||||||
async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
|
async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
|
||||||
|
parts
|
||||||
|
.extensions
|
||||||
|
.get_mut::<crate::vary_middleware::HxTriggerExtracted>()
|
||||||
|
.map(crate::vary_middleware::Notifier::notify);
|
||||||
|
|
||||||
if let Some(trigger) = parts.headers.get(HX_TRIGGER) {
|
if let Some(trigger) = parts.headers.get(HX_TRIGGER) {
|
||||||
if let Ok(trigger) = trigger.to_str() {
|
if let Ok(trigger) = trigger.to_str() {
|
||||||
return Ok(HxTrigger(Some(trigger.to_string())));
|
return Ok(HxTrigger(Some(trigger.to_string())));
|
||||||
|
|
|
@ -9,7 +9,7 @@ use http::{
|
||||||
use tokio::sync::oneshot::{self, Receiver, Sender};
|
use tokio::sync::oneshot::{self, Receiver, Sender};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
headers::{HX_REQUEST_STR, HX_TARGET_STR},
|
headers::{HX_REQUEST_STR, HX_TARGET_STR, HX_TRIGGER_NAME_STR, HX_TRIGGER_STR},
|
||||||
HxError,
|
HxError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -22,6 +22,12 @@ pub(crate) struct HxRequestExtracted(Option<Arc<Sender<()>>>);
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct HxTargetExtracted(Option<Arc<Sender<()>>>);
|
pub(crate) struct HxTargetExtracted(Option<Arc<Sender<()>>>);
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct HxTriggerExtracted(Option<Arc<Sender<()>>>);
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct HxTriggerNameExtracted(Option<Arc<Sender<()>>>);
|
||||||
|
|
||||||
pub trait Notifier {
|
pub trait Notifier {
|
||||||
fn sender(&mut self) -> Option<Sender<()>>;
|
fn sender(&mut self) -> Option<Sender<()>>;
|
||||||
|
|
||||||
|
@ -44,6 +50,18 @@ impl Notifier for HxTargetExtracted {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Notifier for HxTriggerExtracted {
|
||||||
|
fn sender(&mut self) -> Option<Sender<()>> {
|
||||||
|
self.0.take().and_then(Arc::into_inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Notifier for HxTriggerNameExtracted {
|
||||||
|
fn sender(&mut self) -> Option<Sender<()>> {
|
||||||
|
self.0.take().and_then(Arc::into_inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl HxRequestExtracted {
|
impl HxRequestExtracted {
|
||||||
fn insert_into_extensions(extensions: &mut Extensions) -> Receiver<()> {
|
fn insert_into_extensions(extensions: &mut Extensions) -> Receiver<()> {
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
|
@ -64,9 +82,32 @@ impl HxTargetExtracted {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HxTriggerExtracted {
|
||||||
|
fn insert_into_extensions(extensions: &mut Extensions) -> Receiver<()> {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
if extensions.insert(Self(Some(Arc::new(tx)))).is_some() {
|
||||||
|
panic!("{}", MIDDLEWARE_DOUBLE_USE);
|
||||||
|
}
|
||||||
|
rx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HxTriggerNameExtracted {
|
||||||
|
fn insert_into_extensions(extensions: &mut Extensions) -> Receiver<()> {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
if extensions.insert(Self(Some(Arc::new(tx)))).is_some() {
|
||||||
|
panic!("{}", MIDDLEWARE_DOUBLE_USE);
|
||||||
|
}
|
||||||
|
rx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn vary_middleware(mut request: Request, next: Next) -> Response {
|
pub async fn vary_middleware(mut request: Request, next: Next) -> Response {
|
||||||
let hx_request_rx = HxRequestExtracted::insert_into_extensions(request.extensions_mut());
|
let hx_request_rx = HxRequestExtracted::insert_into_extensions(request.extensions_mut());
|
||||||
let hx_target_rx = HxTargetExtracted::insert_into_extensions(request.extensions_mut());
|
let hx_target_rx = HxTargetExtracted::insert_into_extensions(request.extensions_mut());
|
||||||
|
let hx_trigger_rx = HxTriggerExtracted::insert_into_extensions(request.extensions_mut());
|
||||||
|
let hx_trigger_name_rx =
|
||||||
|
HxTriggerNameExtracted::insert_into_extensions(request.extensions_mut());
|
||||||
|
|
||||||
let mut response = next.run(request).await;
|
let mut response = next.run(request).await;
|
||||||
|
|
||||||
|
@ -77,6 +118,12 @@ pub async fn vary_middleware(mut request: Request, next: Next) -> Response {
|
||||||
if hx_target_rx.await.is_ok() {
|
if hx_target_rx.await.is_ok() {
|
||||||
used.push(HX_TARGET_STR)
|
used.push(HX_TARGET_STR)
|
||||||
}
|
}
|
||||||
|
if hx_trigger_rx.await.is_ok() {
|
||||||
|
used.push(HX_TRIGGER_STR)
|
||||||
|
}
|
||||||
|
if hx_trigger_name_rx.await.is_ok() {
|
||||||
|
used.push(HX_TRIGGER_NAME_STR)
|
||||||
|
}
|
||||||
|
|
||||||
if !used.is_empty() {
|
if !used.is_empty() {
|
||||||
let value = match HeaderValue::from_str(&used.join(", ")) {
|
let value = match HeaderValue::from_str(&used.join(", ")) {
|
||||||
|
@ -96,41 +143,82 @@ mod tests {
|
||||||
use axum::{routing::get, Router};
|
use axum::{routing::get, Router};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{HxRequest, HxTarget};
|
use crate::{HxRequest, HxTarget, HxTrigger, HxTriggerName};
|
||||||
|
|
||||||
fn vary_headers(resp: &axum_test::TestResponse) -> Vec<HeaderValue> {
|
fn vary_headers(resp: &axum_test::TestResponse) -> Vec<HeaderValue> {
|
||||||
resp.iter_headers_by_name("vary").cloned().collect()
|
resp.iter_headers_by_name("vary").cloned().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
fn server() -> axum_test::TestServer {
|
||||||
async fn multiple_headers() {
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/no-extractors", get(|| async { () }))
|
.route("/no-extractors", get(|| async { () }))
|
||||||
.route("/single-extractor", get(|_: HxRequest| async { () }))
|
.route("/hx-request", get(|_: HxRequest| async { () }))
|
||||||
// Extractors can be used multiple times e.g. in middlewares
|
.route("/hx-target", get(|_: HxTarget| async { () }))
|
||||||
|
.route("/hx-trigger", get(|_: HxTrigger| async { () }))
|
||||||
|
.route("/hx-trigger-name", get(|_: HxTriggerName| async { () }))
|
||||||
.route(
|
.route(
|
||||||
"/repeated-extractor",
|
"/repeated-extractor",
|
||||||
get(|_: HxRequest, _: HxRequest| async { () }),
|
get(|_: HxRequest, _: HxRequest| async { () }),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/multiple-extractors",
|
"/multiple-extractors",
|
||||||
get(|_: HxRequest, _: HxTarget| async { () }),
|
get(|_: HxRequest, _: HxTarget, _: HxTrigger, _: HxTriggerName| async { () }),
|
||||||
)
|
)
|
||||||
.layer(axum::middleware::from_fn(vary_middleware));
|
.layer(axum::middleware::from_fn(vary_middleware));
|
||||||
let server = axum_test::TestServer::new(app).unwrap();
|
axum_test::TestServer::new(app).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
assert!(vary_headers(&server.get("/no-extractors").await).is_empty());
|
#[tokio::test]
|
||||||
|
async fn no_extractors() {
|
||||||
|
assert!(vary_headers(&server().get("/no-extractors").await).is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn single_hx_request() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
vary_headers(&server.get("/single-extractor").await),
|
vary_headers(&server().get("/hx-request").await),
|
||||||
[HX_REQUEST_STR]
|
["hx-request"]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn single_hx_target() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
vary_headers(&server.get("/repeated-extractor").await),
|
vary_headers(&server().get("/hx-target").await),
|
||||||
[HX_REQUEST_STR]
|
["hx-target"]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn single_hx_trigger() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
vary_headers(&server.get("/multiple-extractors").await),
|
vary_headers(&server().get("/hx-trigger").await),
|
||||||
[format!("{HX_REQUEST_STR}, {HX_TARGET_STR}")]
|
["hx-trigger"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn single_hx_trigger_name() {
|
||||||
|
assert_eq!(
|
||||||
|
vary_headers(&server().get("/hx-trigger-name").await),
|
||||||
|
["hx-trigger-name"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn repeated_extractor() {
|
||||||
|
assert_eq!(
|
||||||
|
vary_headers(&server().get("/repeated-extractor").await),
|
||||||
|
["hx-request"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extractors can be used multiple times e.g. in middlewares
|
||||||
|
#[tokio::test]
|
||||||
|
async fn multiple_extractors() {
|
||||||
|
assert_eq!(
|
||||||
|
vary_headers(&server().get("/multiple-extractors").await),
|
||||||
|
["hx-request, hx-target, hx-trigger, hx-trigger-name"],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue