Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make Inertia Middleware with_shared_props take async callback #8

Merged
merged 6 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
# Changelog

## Unreleased

## v2.2.0
### Changed
- Inertia Middleware `with_shared_props` return type is now a async callback, so that props can be
asynchronously resolved from inside the middleware.

### Breaking Changes
#### Inertia Middleware
When sharing props, instead of:
```rust
InertiaMiddleware::new().with_shared_props(Arc::new(|_req: &ServiceRequest| {
hashmap![ "foo" => InertiaProp::Always("bar".into()) ]
})),
```

do:
```rust
InertiaMiddleware::new().with_shared_props(Arc::new(move |_req: &HttpRequest| {
async move {
hashmap![ "foo" => InertiaProp::Always("bar".into()) ]
}
.boxed_local()
})),
```

## v2.1.0
### Changed
- lowered min required version for `tokio` and `actix-web` crates;
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[package]
version = "2.2.0"
name = "inertia-rust"
description = "A Rust server-adapter for building modern MVC web apps with Inertia."
repository = "https://github.com/KaioFelps/inertia-rust"
keywords = ["inertiajs", "front-end", "react", "svelte", "vue"]
categories = ["network-programming", "template-engine", "visualization", "web-programming", "web-programming::http-server"]
version = "2.1.0"
edition = "2021"
readme = "README.md"
authors = [ "Kaio Felps" ]
Expand Down
16 changes: 6 additions & 10 deletions docs/src/props/shared.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
There are some props that are sent to the front-end on every request. These can be easily shared through
`InertiaMiddleware` rather than conventional props on each request.

The middleware contains a `with_shared_props` method, which requires a callback --- wrapped in an `Arc`
--- that receives a reference to the current request. You can use it to extract any information you might
want to share.
The middleware contains a `with_shared_props` method, which requires a callback that receives a reference
to the current HTTP request. You can use it to extract any information you might want to share.

```rust
use actix_web::{App, HttpServer};
Expand All @@ -24,17 +23,14 @@ async fn main() -> std::io::Result<()> {
// depending on your opted framework
let session = req.get_session();
let flash = serde_json::to_value(session.get::<String>("flash").unwrap()).unwrap();

hashmap![
"flash" => InertiaProp::data(flash)
]
async move {
hashmap![ "flash" => InertiaProp::data(flash) ]
}
.boxed_local()
})))
})
.bind(("127.0.0.1", 3000))?
.run()
.await
}
```

Although you cannot do asynchronous code inside this callback, you can still use them inside lazy
`InertiaProp` variants (`Lazy`, `Demand`, `Deferred`).
2 changes: 1 addition & 1 deletion src/providers/actix_provider/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ impl InertiaHttpRequest for HttpRequest {
fn check_inertia_version(&self, current_version: &str) -> bool {
self.headers()
.get(headers::X_INERTIA_VERSION)
.map_or(true, |version| {
.is_none_or(|version| {
version
.to_str()
.is_ok_and(|version| version == current_version)
Expand Down
50 changes: 29 additions & 21 deletions src/providers/actix_provider/middleware.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
use actix_web::dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform};
use actix_web::http::{Method, StatusCode};
use actix_web::Error;
use actix_web::HttpMessage;
use actix_web::{Error, HttpRequest};
use futures::FutureExt;
use futures_util::future::LocalBoxFuture;
use serde_json::to_value;
use std::collections::HashMap;
use std::future::{ready, Ready};
use std::future::{ready, Future, Ready};
use std::pin::Pin;
use std::rc::Rc;
use std::sync::Arc;

use crate::temporary_session::InertiaTemporarySession;
use crate::{InertiaProp, InertiaProps};

type SharedPropsCallback<'a> = dyn Fn(&ServiceRequest) -> InertiaProps<'a>;
type SharedPropsCallback<'a> =
Arc<dyn Fn(&HttpRequest) -> Pin<Box<dyn Future<Output = InertiaProps<'a>>>>>;

pub struct InertiaMiddleware<'a> {
shared_props_cb: Arc<SharedPropsCallback<'a>>,
shared_props_cb: SharedPropsCallback<'a>,
}

impl Default for InertiaMiddleware<'_> {
Expand All @@ -26,11 +30,11 @@ impl Default for InertiaMiddleware<'_> {
impl<'a> InertiaMiddleware<'a> {
pub fn new() -> Self {
Self {
shared_props_cb: Arc::new(|_req| HashMap::new()),
shared_props_cb: Arc::new(move |_req| async move { HashMap::new() }.boxed()),
}
}

pub fn with_shared_props(mut self, props: Arc<SharedPropsCallback<'a>>) -> Self {
pub fn with_shared_props(mut self, props: SharedPropsCallback<'a>) -> Self {
self.shared_props_cb = props;
self
}
Expand All @@ -41,7 +45,7 @@ impl<'a> InertiaMiddleware<'a> {
// `B` - type of response's body
impl<'a, S, B> Transform<S, ServiceRequest> for InertiaMiddleware<'a>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'a,
S::Future: 'static,
B: 'static,
'a: 'static,
Expand All @@ -55,22 +59,22 @@ where
fn new_transform(&self, service: S) -> Self::Future {
let shpcb = self.shared_props_cb.clone();
ready(Ok(InertiaMiddlewareService {
service,
service: Rc::new(service),
shared_props: shpcb,
}))
}
}

pub struct InertiaMiddlewareService<'a, S> {
service: S,
shared_props: Arc<SharedPropsCallback<'a>>,
service: Rc<S>,
shared_props: SharedPropsCallback<'a>,
}

pub(crate) struct SharedProps<'a>(pub InertiaProps<'a>);

impl<'a, S, B> Service<ServiceRequest> for InertiaMiddlewareService<'a, S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'a,
S::Future: 'static,
B: 'static,
'a: 'static,
Expand All @@ -82,19 +86,22 @@ where
forward_ready!(service);

fn call(&self, req: ServiceRequest) -> Self::Future {
let mut shared_props = (self.shared_props)(&req);
let srvc = self.service.clone();
let shared_props = self.shared_props.clone();

if let Some(request_props) = req.extensions().get::<InertiaTemporarySession>() {
let errors = to_value(&request_props.errors).unwrap();
shared_props.insert("errors", InertiaProp::Always(errors));
}
async move {
let mut shared_props = shared_props(req.request()).await;

if let Some(request_props) = req.extensions().get::<InertiaTemporarySession>() {
let errors = to_value(&request_props.errors).unwrap();
shared_props.insert("errors", InertiaProp::Always(errors));
}

req.extensions_mut().insert(SharedProps(shared_props));
req.extensions_mut().insert(SharedProps(shared_props));

let fut: <S as Service<ServiceRequest>>::Future = self.service.call(req);
let fut = srvc.call(req);

Box::pin(async move {
let mut res: ServiceResponse<B> = fut.await?;
let mut res = fut.await?;

let req_method = res.request().method();
let res_status = res.status();
Expand All @@ -107,6 +114,7 @@ where
}

Ok(res)
})
}
.boxed_local()
}
}
16 changes: 12 additions & 4 deletions tests/test_p_actix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use actix_web::{
App, HttpMessage, HttpRequest, HttpResponse, Responder,
};
use common::template_resolver::{get_dynamic_csr_expect, MockedTemplateResolver};
use futures::FutureExt;
use inertia_rust::actix::SessionErrors;
use inertia_rust::{
actix::{EncryptHistoryMiddleware, InertiaHeader, InertiaMiddleware},
Expand Down Expand Up @@ -419,16 +420,23 @@ async fn test_render_with_props() {
);
}

async fn foo(_baz: &HttpRequest) {}

#[tokio::test]
async fn test_shared_props() {
const TEST_SHARED_PROPERTY_KEY: &str = "sharedProperty";
const TEST_SHARED_PROPERTY_VALUE: &str = "Some amazing value!";

let app = actix_web::test::init_service(generate_actix_app().await.wrap(
InertiaMiddleware::new().with_shared_props(Arc::new(|_req| {
hashmap![
TEST_SHARED_PROPERTY_KEY => InertiaProp::Always(TEST_SHARED_PROPERTY_VALUE.into()),
]
InertiaMiddleware::new().with_shared_props(Arc::new(move |req| {
let req = req.clone();
async move {
foo(&req).await;
hashmap![
TEST_SHARED_PROPERTY_KEY => InertiaProp::Always(TEST_SHARED_PROPERTY_VALUE.into()),
]
}
.boxed_local()
})),
))
.await;
Expand Down
Loading