diff --git a/integrations/actix/src/lib.rs b/integrations/actix/src/lib.rs index af4b26359f..418aa10ac7 100644 --- a/integrations/actix/src/lib.rs +++ b/integrations/actix/src/lib.rs @@ -27,7 +27,12 @@ use leptos_meta::*; use leptos_router::*; use parking_lot::RwLock; use regex::Regex; -use std::{fmt::Display, future::Future, pin::Pin, sync::Arc}; +use std::{ + fmt::{Debug, Display}, + future::Future, + pin::Pin, + sync::Arc, +}; #[cfg(debug_assertions)] use tracing::instrument; /// This struct lets you define headers and override the status of the Response from an Element or a Server Function @@ -1410,6 +1415,58 @@ where Ok(f.call(input).await) } +/// A helper to make it easier to use Axum extractors in server functions, with a +/// simpler API than [`extract`]. +/// +/// It is generic over some type `T` that implements [`FromRequestParts`] and can +/// therefore be used in an extractor. The compiler can often infer this type. +/// +/// Any error that occurs during extraction is converted to a [`ServerFnError`]. +/// +/// ```rust,ignore +/// // MyQuery is some type that implements `Deserialize + Serialize` +/// #[server] +/// pub async fn query_extract() -> Result { +/// use actix_web::web::Query; +/// use leptos_actix::*; +/// let Query(data) = extractor().await?; +/// Ok(data) +/// } +/// ``` +pub async fn extractor() -> Result +where + T: actix_web::FromRequest, + ::Error: Debug, +{ + let req = use_context::() + .expect("HttpRequest should have been provided via context"); + + if let Some(body) = use_context::() { + let (_, mut payload) = actix_http::h1::Payload::create(false); + payload.unread_data(body); + T::from_request(&req, &mut dev::Payload::from(payload)) + } else { + T::extract(&req) + } + .await + .map_err(|e| ServerFnError::ServerError(format!("{e:?}"))) +} + +/// A macro that makes it easier to use extractors in server functions. The macro +/// takes a type or types, and extracts them from the request, returning from the +/// server function with an `Err(_)` if there is an error during extraction. +/// ```rust,ignore +/// let info = extract!(ConnectionInfo); +/// let Query(data) = extract!(Query); +/// let (info, Query(data)) = extract!(ConnectionInfo, Query); +/// ``` +#[macro_export] +macro_rules! extract { + ($($x:ty),+) => { + $crate::extract(|fields: ($($x),+)| async move { fields }).await? + }; +} + // Drawn from the Actix Handler implementation // https://github.com/actix/actix-web/blob/19c9d858f25e8262e14546f430d713addb397e96/actix-web/src/handler.rs#L124 pub trait Extractor { @@ -1417,6 +1474,7 @@ pub trait Extractor { fn call(self, args: T) -> Self::Future; } + macro_rules! factory_tuple ({ $($param:ident)* } => { impl Extractor<($($param,)*)> for Func where diff --git a/integrations/axum/Cargo.toml b/integrations/axum/Cargo.toml index d1fac2d9bd..cc410e8742 100644 --- a/integrations/axum/Cargo.toml +++ b/integrations/axum/Cargo.toml @@ -8,7 +8,9 @@ repository = "https://github.com/leptos-rs/leptos" description = "Axum integrations for the Leptos web framework." [dependencies] -axum = { version = "0.6", default-features = false, features=["matched-path"] } +axum = { version = "0.6", default-features = false, features = [ + "matched-path", +] } futures = "0.3" http = "0.2.8" hyper = "0.14.23" @@ -28,4 +30,4 @@ cfg-if = "1.0.0" nonce = ["leptos/nonce"] wasm = [] default = ["tokio/full", "axum/macros"] -experimental-islands = ["leptos_integration_utils/experimental-islands"] \ No newline at end of file +experimental-islands = ["leptos_integration_utils/experimental-islands"] diff --git a/integrations/axum/src/lib.rs b/integrations/axum/src/lib.rs index 74a4aa9f76..fce9801450 100644 --- a/integrations/axum/src/lib.rs +++ b/integrations/axum/src/lib.rs @@ -35,7 +35,7 @@ use leptos_meta::{generate_head_metadata_separated, MetaContext}; use leptos_router::*; use once_cell::sync::OnceCell; use parking_lot::RwLock; -use std::{io, pin::Pin, sync::Arc, thread::available_parallelism}; +use std::{fmt::Debug, io, pin::Pin, sync::Arc, thread::available_parallelism}; use tokio_util::task::LocalPoolHandle; use tracing::Instrument; /// A struct to hold the parts of the incoming Request. Since `http::Request` isn't cloneable, we're forced @@ -1826,6 +1826,40 @@ where extract_with_state((), f).await } +/// A helper to make it easier to use Axum extractors in server functions, with a +/// simpler API than [`extract`]. +/// +/// It is generic over some type `T` that implements [`FromRequestParts`] and can +/// therefore be used in an extractor. The compiler can often infer this type. +/// +/// Any error that occurs during extraction is converted to a [`ServerFnError`]. +/// +/// ```rust,ignore +/// // MyQuery is some type that implements `Deserialize + Serialize` +/// #[server] +/// pub async fn query_extract() -> Result { +/// use axum::{extract::Query, http::Method}; +/// use leptos_axum::*; +/// let Query(query) = extractor().await?; +/// +/// Ok(query) +/// } +/// ``` +pub async fn extractor() -> Result +where + T: Sized + FromRequestParts<()>, + T::Rejection: Debug, +{ + let ctx = use_context::().expect( + "should have had ExtractorHelper provided by the leptos_axum \ + integration", + ); + let mut parts = ctx.parts.lock().await; + T::from_request_parts(&mut parts, &()) + .await + .map_err(|e| ServerFnError::ServerError(format!("{e:?}"))) +} + /// A helper to make it easier to use Axum extractors in server functions. This takes /// a handler function and state as its arguments. The handler rules similar to Axum /// [handlers](https://docs.rs/axum/latest/axum/extract/index.html#intro): it is an async function