codify "platform" into resolution logic, server-fn/fullstack overhaul (#4596)
overhaul dioxus fullstack using a new implementation of server_fn This PR is a complete rewrite of dioxus fullstack designed to improve compatibility with axum and to fix a number of compilation errors based on feature flag unification plumbing through macros.
This commit is contained in:
1 parent
8421c3aef0
commit
f2b1c6d97f
424 files changed
+17284
-6624
No files matched your search
@@ -0,0 +1,171 @@
|
||||
use dioxus_fullstack_core::{RequestError, ServerFnError};
|
||||
#[cfg(feature = "server")]
|
||||
use headers::Header;
|
||||
use http::response::Parts;
|
||||
use std::{future::Future, pin::Pin};
|
||||
|
||||
use crate::{ClientRequest, ClientResponse};
|
||||
|
||||
/// The `IntoRequest` trait allows types to be used as the body of a request to a HTTP endpoint or server function.
|
||||
///
|
||||
/// `IntoRequest` allows for types handle the calling of `ClientRequest::send` where the result is then
|
||||
/// passed to `FromResponse` to decode the response.
|
||||
///
|
||||
/// You can think of the `IntoRequest` and `FromResponse` traits are "inverse" traits of the axum
|
||||
/// `FromRequest` and `IntoResponse` traits. Just like a type can be decoded from a request via `FromRequest`,
|
||||
/// a type can be encoded into a request via `IntoRequest`.
|
||||
///
|
||||
/// ## Generic State
|
||||
///
|
||||
/// `IntoRequest` is generic over the response type `R` which defaults to `ClientResponse`. The default
|
||||
/// `ClientResponse` is the base response type that internally wraps `reqwest::Response`.
|
||||
///
|
||||
/// However, some responses might need state from the initial request to properly decode the response.
|
||||
/// Most state can be extended via the `.extension()` method on `ClientRequest`. In some cases, like
|
||||
/// websockets, the response needs to retain an initial connection from the request. Here, you can use
|
||||
/// the `R` generic to specify a concrete response type. The resulting type that implements `FromResponse`
|
||||
/// must also be generic over the same `R` type.
|
||||
pub trait IntoRequest<R = ClientResponse>: Sized {
|
||||
fn into_request(
|
||||
self,
|
||||
req: ClientRequest,
|
||||
) -> impl Future<Output = Result<R, RequestError>> + 'static;
|
||||
}
|
||||
|
||||
impl<A, R> IntoRequest<R> for (A,)
|
||||
where
|
||||
A: IntoRequest<R> + 'static,
|
||||
{
|
||||
fn into_request(
|
||||
self,
|
||||
req: ClientRequest,
|
||||
) -> impl Future<Output = Result<R, RequestError>> + 'static {
|
||||
A::into_request(self.0, req)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FromResponse<R = ClientResponse>: Sized {
|
||||
fn from_response(res: R) -> impl Future<Output = Result<Self, ServerFnError>>;
|
||||
}
|
||||
|
||||
impl<A> FromResponse for A
|
||||
where
|
||||
A: FromResponseParts,
|
||||
{
|
||||
fn from_response(res: ClientResponse) -> impl Future<Output = Result<Self, ServerFnError>> {
|
||||
async move {
|
||||
let (parts, _body) = res.into_parts();
|
||||
let mut parts = parts;
|
||||
A::from_response_parts(&mut parts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B> FromResponse for (A, B)
|
||||
where
|
||||
A: FromResponseParts,
|
||||
B: FromResponse,
|
||||
{
|
||||
fn from_response(res: ClientResponse) -> impl Future<Output = Result<Self, ServerFnError>> {
|
||||
async move {
|
||||
let mut parts = res.make_parts();
|
||||
let a = A::from_response_parts(&mut parts)?;
|
||||
let b = B::from_response(res).await?;
|
||||
Ok((a, b))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B, C> FromResponse for (A, B, C)
|
||||
where
|
||||
A: FromResponseParts,
|
||||
B: FromResponseParts,
|
||||
C: FromResponse,
|
||||
{
|
||||
fn from_response(res: ClientResponse) -> impl Future<Output = Result<Self, ServerFnError>> {
|
||||
async move {
|
||||
let mut parts = res.make_parts();
|
||||
let a = A::from_response_parts(&mut parts)?;
|
||||
let b = B::from_response_parts(&mut parts)?;
|
||||
let c = C::from_response(res).await?;
|
||||
Ok((a, b, c))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FromResponseParts
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn from_response_parts(parts: &mut Parts) -> Result<Self, ServerFnError>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
impl<T: Header> FromResponseParts for axum_extra::TypedHeader<T> {
|
||||
fn from_response_parts(parts: &mut Parts) -> Result<Self, ServerFnError> {
|
||||
use headers::HeaderMapExt;
|
||||
|
||||
let t = parts
|
||||
.headers
|
||||
.typed_get::<T>()
|
||||
.ok_or_else(|| ServerFnError::Serialization("Invalid header value".into()))?;
|
||||
|
||||
Ok(axum_extra::TypedHeader(t))
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
todo: make the serverfns return ServerFnRequest which lets us control the future better
|
||||
*/
|
||||
#[pin_project::pin_project]
|
||||
#[must_use = "Requests do nothing unless you `.await` them"]
|
||||
pub struct ServerFnRequest<Output> {
|
||||
_phantom: std::marker::PhantomData<Output>,
|
||||
#[pin]
|
||||
fut: Pin<Box<dyn Future<Output = Output> + Send>>,
|
||||
}
|
||||
|
||||
impl<O> ServerFnRequest<O> {
|
||||
pub fn new(res: impl Future<Output = O> + Send + 'static) -> Self {
|
||||
ServerFnRequest {
|
||||
_phantom: std::marker::PhantomData,
|
||||
fut: Box::pin(res),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> std::future::Future for ServerFnRequest<Result<T, E>> {
|
||||
type Output = Result<T, E>;
|
||||
|
||||
fn poll(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Self::Output> {
|
||||
self.project().fut.poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[diagnostic::on_unimplemented(
|
||||
message = "The return type of a server function must be `Result<T, E>`",
|
||||
note = "`T` is either `impl IntoResponse` *or* `impl Serialize`",
|
||||
note = "`E` is either `From<ServerFnError> + Serialize`, `dioxus::Error` or `StatusCode`."
|
||||
)]
|
||||
pub trait AssertIsResult {}
|
||||
impl<T, E> AssertIsResult for Result<T, E> {}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn assert_is_result<T: AssertIsResult>() {}
|
||||
|
||||
#[diagnostic::on_unimplemented(
|
||||
message = "The arguments to the server function must either be a single `impl FromRequest + IntoRequest` argument, or multiple `DeserializeOwned` arguments."
|
||||
)]
|
||||
pub trait AssertCanEncode {}
|
||||
|
||||
pub struct CantEncode;
|
||||
|
||||
pub struct EncodeIsVerified;
|
||||
impl AssertCanEncode for EncodeIsVerified {}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn assert_can_encode(_t: impl AssertCanEncode) {}
|
||||
Reference in new issue
Block a user