feat: support Axum extractors with state other than () (#1275)

This requires state to be provided via context using a special handler, but allows for extractors that use this state, rather than only `()`, as previously.
This commit is contained in:
sjud 2023-07-05 20:40:29 -04:00 committed by GitHub
parent f06ffd72aa
commit 7e540a8f49
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -1276,18 +1276,24 @@ impl ExtractorHelper {
}
}
pub async fn extract<F, T, U>(&self, f: F) -> Result<U, T::Rejection>
pub async fn extract<F, T, U, S>(
&self,
f: F,
s: S,
) -> Result<U, T::Rejection>
where
F: Extractor<T, U>,
T: std::fmt::Debug + Send + FromRequestParts<()> + 'static,
S: Sized,
F: Extractor<T, U, S>,
T: std::fmt::Debug + Send + FromRequestParts<S> + 'static,
T::Rejection: std::fmt::Debug + Send + 'static,
{
let mut parts = self.parts.lock().await;
let data = T::from_request_parts(&mut parts, &()).await?;
let data = T::from_request_parts(&mut parts, &s).await?;
Ok(f.call(data).await)
}
}
/// Getting ExtractorHelper from a request will return an ExtractorHelper whose state is ().
impl<B> From<Request<B>> for ExtractorHelper {
fn from(req: Request<B>) -> Self {
// TODO provide body for extractors there, too?
@ -1324,34 +1330,71 @@ impl<B> From<Request<B>> for ExtractorHelper {
#[tracing::instrument(level = "trace", fields(error), skip_all)]
pub async fn extract<T, U>(
cx: Scope,
f: impl Extractor<T, U>,
f: impl Extractor<T, U, ()>,
) -> Result<U, T::Rejection>
where
T: std::fmt::Debug + Send + FromRequestParts<()> + 'static,
T::Rejection: std::fmt::Debug + Send + 'static,
{
extract_with_state(cx, (), f).await
}
/// 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
/// whose arguments are “extractors.”
///
/// ```rust,ignore
/// #[server(QueryExtract, "/api")]
/// pub async fn query_extract(cx: Scope) -> Result<String, ServerFnError> {
/// use axum::{extract::Query, http::Method};
/// use leptos_axum::extract;
/// let state: ServerState = use_context::<crate::ServerState>(cx)
/// .ok_or(ServerFnError::ServerError("No server state".to_string()))?;
///
/// extract_with_state(cx, state, |method: Method, res: Query<MyQuery>| async move {
/// format!("{method:?} and {}", res.q)
/// },
/// )
/// .await
/// .map_err(|e| ServerFnError::ServerError("Could not extract method and query...".to_string()))
/// }
/// ```
#[tracing::instrument(level = "trace", fields(error), skip_all)]
pub async fn extract_with_state<T, U, S>(
cx: Scope,
state: S,
f: impl Extractor<T, U, S>,
) -> Result<U, T::Rejection>
where
S: Sized,
T: std::fmt::Debug + Send + FromRequestParts<S> + 'static,
T::Rejection: std::fmt::Debug + Send + 'static,
{
use_context::<ExtractorHelper>(cx)
.expect(
"should have had ExtractorHelper provided by the leptos_axum \
integration",
)
.extract(f)
.extract(f, state)
.await
}
pub trait Extractor<T, U>
pub trait Extractor<T, U, S>
where
T: FromRequestParts<()>,
S: Sized,
T: FromRequestParts<S>,
{
fn call(self, args: T) -> Pin<Box<dyn Future<Output = U>>>;
}
macro_rules! factory_tuple ({ $($param:ident)* } => {
impl<Func, Fut, U, $($param,)*> Extractor<($($param,)*), U> for Func
impl<Func, Fut, U, S, $($param,)*> Extractor<($($param,)*), U, S> for Func
where
$($param: FromRequestParts<()> + Send,)*
$($param: FromRequestParts<S> + Send,)*
Func: FnOnce($($param),*) -> Fut + 'static,
Fut: Future<Output = U> + 'static,
S: Sized + Send + Sync,
{
#[inline]
#[allow(non_snake_case)]