feat: add extractor functions with better API than extract (closes #1755) (#1859)

This commit is contained in:
Greg Johnston 2023-10-07 13:10:30 -04:00 committed by GitHub
parent c7607f6fcc
commit 0d4862b238
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 98 additions and 4 deletions

View file

@ -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<MyQuery, ServerFnError> {
/// use actix_web::web::Query;
/// use leptos_actix::*;
/// let Query(data) = extractor().await?;
/// Ok(data)
/// }
/// ```
pub async fn extractor<T>() -> Result<T, ServerFnError>
where
T: actix_web::FromRequest,
<T as FromRequest>::Error: Debug,
{
let req = use_context::<actix_web::HttpRequest>()
.expect("HttpRequest should have been provided via context");
if let Some(body) = use_context::<Bytes>() {
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<Search>);
/// let (info, Query(data)) = extract!(ConnectionInfo, Query<Search>);
/// ```
#[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<T> {
@ -1417,6 +1474,7 @@ pub trait Extractor<T> {
fn call(self, args: T) -> Self::Future;
}
macro_rules! factory_tuple ({ $($param:ident)* } => {
impl<Func, Fut, $($param,)*> Extractor<($($param,)*)> for Func
where

View file

@ -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"]
experimental-islands = ["leptos_integration_utils/experimental-islands"]

View file

@ -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<MyQuery, ServerFnError> {
/// use axum::{extract::Query, http::Method};
/// use leptos_axum::*;
/// let Query(query) = extractor().await?;
///
/// Ok(query)
/// }
/// ```
pub async fn extractor<T>() -> Result<T, ServerFnError>
where
T: Sized + FromRequestParts<()>,
T::Rejection: Debug,
{
let ctx = use_context::<ExtractorHelper>().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