feat: middleware

This commit is contained in:
davidontop 2024-07-05 10:11:17 +02:00
parent 7c76a4a04e
commit 4e65532679
Signed by: DavidOnTop
GPG key ID: 5D05538A45D5149F
11 changed files with 138 additions and 39 deletions

3
Cargo.lock generated
View file

@ -641,7 +641,7 @@ dependencies = [
[[package]]
name = "leptos_reactive_axum"
version = "0.1.0"
version = "1.0.0"
dependencies = [
"axum",
"axum-test",
@ -652,6 +652,7 @@ dependencies = [
"serde_json",
"thiserror",
"tokio",
"tower",
]
[[package]]

View file

@ -3,7 +3,7 @@ members = ["macros", "."]
[package]
name = "leptos_reactive_axum"
version = "0.1.0"
version = "1.0.0"
edition = "2021"
description = "reactive context for axum handlers"
authors = ["davidontop <me@davidon.top>"]
@ -13,15 +13,19 @@ license = "MIT"
repository = "https://git.davidon.top/public/leptos_reactive_axum.git"
[dependencies]
leptos_reactive_axum_macros = { path = "./macros", version = "0" }
leptos_reactive_axum_macros = { path = "./macros", version = "0", optional = true }
leptos_reactive = { version = "0.6", features = ["ssr"] }
axum = "0.7"
scopeguard = "1.2.0"
thiserror = "1.0.61"
http = "1.1.0"
tower = { version = "0.4.13", optional = true }
[features]
default = ["middleware"]
nightly = ["leptos_reactive/nightly"]
macros = ["dep:leptos_reactive_axum_macros"]
middleware = ["dep:tower"]
[dev-dependencies]
axum-test = "15.1.0"

View file

@ -1 +1,5 @@
# ctxt
# whoami
Library providing utilities for using leptos_reactive runtime inside axum handlers. Also automaticly uses provide_context to make the request Parts available inside any function called by the handler.
# But why?
When using something like the [rstml-component](https://crates.io/crates/rstml-component) crate it is usefull to be able to extract anything from parts inside any component. For example inside nav bar extract the path to be able to highlight active route. Additionally you can create a middleware layer that can add to the context things like IsLoggedIn or other information about the request that is used within more then one function/component called by the handler.

View file

@ -1,6 +1,6 @@
[package]
name = "leptos_reactive_axum_macros"
version = "0.1.0"
version = "1.0.0"
edition = "2021"
description = "reactive context for axum handlers"
authors = ["davidontop <me@davidon.top>"]

View file

@ -1,6 +1,7 @@
mod reactive;
/// macro that when applied on an axum handler will provide leptos_reactive runtime and will allow you to use `leptos_reactive_axum::extract` by providing a context holding request parts
/// WARNING: inserts .unwrap() into handlers to make types the same as in function arguments, use middleware to avoid this
#[proc_macro_attribute]
pub fn reactive(
attr: proc_macro::TokenStream,

View file

@ -1,9 +1,12 @@
pub mod error;
#[cfg(feature = "middleware")]
pub mod middleware;
use std::fmt::Debug;
use axum::{extract::FromRequestParts, http::request::Parts};
use error::ExtractionError;
#[cfg(feature = "macros")]
pub use leptos_reactive_axum_macros::reactive;
/// used to extract request parts from handlers, should be used in conjunction with `leptos_reactive_axum_macros::reactive macro`

52
src/middleware.rs Normal file
View file

@ -0,0 +1,52 @@
use std::{future::Future, pin::Pin};
use axum::{extract::Request, response::Response};
use tower::{Layer, Service};
/// ```rust
/// axum::Router::new().layer(ReactiveLayer);
/// ```
#[derive(Clone)]
pub struct ReactiveLayer;
impl<S> Layer<S> for ReactiveLayer {
type Service = ReactiveMiddleware<S>;
fn layer(&self, inner: S) -> Self::Service {
ReactiveMiddleware {inner}
}
}
#[derive(Clone)]
pub struct ReactiveMiddleware<S> {
inner: S,
}
impl<S> Service<Request> for ReactiveMiddleware<S>
where
S: Service<Request, Response = Response> + Send + 'static,
S::Future: Send + 'static,
{
type Response = S::Response;
type Error = S::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
fn poll_ready(&mut self, cx: &mut std::task::Context<'_>) -> std::task::Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: Request) -> Self::Future {
let __runtime = leptos_reactive::create_runtime();
let (parts, body) = req.into_parts();
leptos_reactive::provide_context(parts.clone());
let req = Request::from_parts(parts, body);
let future = self.inner.call(req);
Box::pin(async move {
let response: Response = future.await?;
__runtime.dispose();
Ok(response)
})
}
}

34
tests/middleware.rs Normal file
View file

@ -0,0 +1,34 @@
use axum::{response::IntoResponse, routing::get, Json, Router};
use axum_test::TestServer;
use http::{HeaderMap, HeaderName, HeaderValue};
use leptos_reactive_axum::extract;
use serde_json::{json, Value};
async fn handler(Json(json): Json<Value>) -> impl IntoResponse {
let headers: HeaderMap = extract().await.unwrap();
assert_eq!(
headers.get("macrosareawesome"),
Some(&HeaderValue::from_static("YehTheyAre"))
);
assert_eq!(json.as_str(), Some("hello there"));
""
}
#[tokio::test]
async fn test_handlers() {
let router = Router::new()
.route("/", get(handler))
.layer(leptos_reactive_axum::middleware::ReactiveLayer);
let server = TestServer::new(router).unwrap();
let _ = server
.get("/")
.add_header(
HeaderName::from_static("macrosareawesome"),
HeaderValue::from_static("YehTheyAre"),
)
.json(&json!("hello there"));
}