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: add extractor functions with better API than extract #1859

Merged
merged 3 commits into from
Oct 7, 2023
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
60 changes: 59 additions & 1 deletion integrations/actix/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1410,13 +1415,66 @@ 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> {
type Future;

fn call(self, args: T) -> Self::Future;
}

macro_rules! factory_tuple ({ $($param:ident)* } => {
impl<Func, Fut, $($param,)*> Extractor<($($param,)*)> for Func
where
Expand Down
6 changes: 4 additions & 2 deletions integrations/axum/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"]
36 changes: 35 additions & 1 deletion integrations/axum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down