multiple fullstack nits/cleanups before release (#4852)

* feat: `:arg` syntax for serverfn

* add better decode/encode verify message

* allow catch-all query args

* add rebind

* implement websocket frames for `Streaming`

* fix lazy init

* wip... serverfnstate

* wip...

* wip: serverfns require Send/Sync, let's fix that

* wip...

* small cleanup

* fix hang by actually spawning the tokio task

* clippy

* fix parameter catchall binding

* fix lychee

* doc tests
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2025-10-30 18:52:47 -07:00
1 parent 43373f0be7
commit 5559e463c8
27 files changed
+1395 -1097

No files matched your search

+61 -45
View File
@@ -42,7 +42,7 @@ use crate::{
use axum::response::IntoResponse;
use axum_core::extract::{FromRequest, Request};
use bytes::Bytes;
use dioxus_fullstack_core::{DioxusServerState, RequestError};
use dioxus_fullstack_core::RequestError;
use http::StatusCode;
use send_wrapper::SendWrapper;
use serde::Serialize;
@@ -172,7 +172,7 @@ pub mod req_to {
impl<T, O, R> EncodeRequest<T, O, R> for &&&&&&&&&ServerFnEncoder<T, O>
where
T: 'static,
O: FromRequest<DioxusServerState> + IntoRequest<R>,
O: IntoRequest<R>,
{
type VerifyEncode = EncodeIsVerified;
fn fetch_client(
@@ -214,6 +214,8 @@ pub mod req_to {
pub use decode_ok::*;
mod decode_ok {
use crate::{CantDecode, DecodeIsVerified};
use super::*;
/// Convert the reqwest response into the desired type, in place.
@@ -222,13 +224,16 @@ mod decode_ok {
/// This is because FromResponse types are more specialized and can handle things like websockets and files.
/// DeserializeOwned types are more general and can handle things like JSON responses.
pub trait RequestDecodeResult<T, R> {
type VerifyDecode;
fn decode_client_response(
&self,
res: Result<R, RequestError>,
) -> impl Future<Output = Result<Result<T, ServerFnError>, RequestError>> + Send;
fn verify_can_deserialize(&self) -> Self::VerifyDecode;
}
impl<T: FromResponse<R>, E, R> RequestDecodeResult<T, R> for &&&ServerFnDecoder<Result<T, E>> {
type VerifyDecode = DecodeIsVerified;
fn decode_client_response(
&self,
res: Result<R, RequestError>,
@@ -240,11 +245,15 @@ mod decode_ok {
}
})
}
fn verify_can_deserialize(&self) -> Self::VerifyDecode {
DecodeIsVerified
}
}
impl<T: DeserializeOwned, E> RequestDecodeResult<T, ClientResponse>
for &&ServerFnDecoder<Result<T, E>>
{
type VerifyDecode = DecodeIsVerified;
fn decode_client_response(
&self,
res: Result<ClientResponse, RequestError>,
@@ -304,6 +313,24 @@ mod decode_ok {
}
})
}
fn verify_can_deserialize(&self) -> Self::VerifyDecode {
DecodeIsVerified
}
}
impl<T, R, E> RequestDecodeResult<T, R> for &ServerFnDecoder<Result<T, E>> {
type VerifyDecode = CantDecode;
fn decode_client_response(
&self,
_res: Result<R, RequestError>,
) -> impl Future<Output = Result<Result<T, ServerFnError>, RequestError>> + Send {
async move { unimplemented!() }
}
fn verify_can_deserialize(&self) -> Self::VerifyDecode {
CantDecode
}
}
pub trait RequestDecodeErr<T, E> {
@@ -460,33 +487,34 @@ pub use req_from::*;
pub mod req_from {
use super::*;
use axum::{extract::FromRequestParts, response::Response};
use dioxus_fullstack_core::FullstackContext;
pub trait ExtractRequest<In, Out, H, M = ()> {
fn extract_axum(
&self,
state: DioxusServerState,
state: FullstackContext,
request: Request,
map: fn(In) -> Out,
) -> impl Future<Output = Result<(H, Out), Response>> + 'static;
) -> impl Future<Output = Result<(Out, H), Response>> + 'static;
}
/// When you're extracting entirely on the server, we need to reject client-consuning request bodies
/// This sits above priority in the combined headers on server / body on client case.
impl<In, M, H> ExtractRequest<In, (), H, M> for &&&&&&&&&&&ServerFnEncoder<In, ()>
where
H: FromRequest<DioxusServerState, M> + 'static,
H: FromRequest<FullstackContext, M> + 'static,
{
fn extract_axum(
&self,
state: DioxusServerState,
state: FullstackContext,
request: Request,
_map: fn(In) -> (),
) -> impl Future<Output = Result<(H, ()), Response>> + 'static {
) -> impl Future<Output = Result<((), H), Response>> + 'static {
async move {
H::from_request(request, &state)
.await
.map_err(|e| e.into_response())
.map(|out| (out, ()))
.map(|out| ((), out))
}
}
}
@@ -496,14 +524,14 @@ pub mod req_from {
where
In: DeserializeOwned + 'static,
Out: 'static,
H: FromRequestParts<DioxusServerState>,
H: FromRequestParts<FullstackContext>,
{
fn extract_axum(
&self,
_state: DioxusServerState,
_state: FullstackContext,
request: Request,
map: fn(In) -> Out,
) -> impl Future<Output = Result<(H, Out), Response>> + 'static {
) -> impl Future<Output = Result<(Out, H), Response>> + 'static {
async move {
let (mut parts, body) = request.into_parts();
let headers = H::from_request_parts(&mut parts, &_state)
@@ -525,7 +553,7 @@ pub mod req_from {
.map_err(|e| ServerFnError::from(e).into_response())
.unwrap();
Ok((headers, out))
Ok((out, headers))
}
}
}
@@ -533,15 +561,15 @@ pub mod req_from {
/// We skip the BodySerialize wrapper and just go for the output type directly.
impl<In, Out, M, H> ExtractRequest<In, Out, H, M> for &&&&&&&&&ServerFnEncoder<In, Out>
where
Out: FromRequest<DioxusServerState, M> + 'static,
H: FromRequestParts<DioxusServerState>,
Out: FromRequest<FullstackContext, M> + 'static,
H: FromRequestParts<FullstackContext>,
{
fn extract_axum(
&self,
state: DioxusServerState,
state: FullstackContext,
request: Request,
_map: fn(In) -> Out,
) -> impl Future<Output = Result<(H, Out), Response>> + 'static {
) -> impl Future<Output = Result<(Out, H), Response>> + 'static {
async move {
let (mut parts, body) = request.into_parts();
let headers = H::from_request_parts(&mut parts, &state)
@@ -554,7 +582,7 @@ pub mod req_from {
.await
.map_err(|e| e.into_response());
res.map(|out| (headers, out))
res.map(|out| (out, headers))
}
}
}
@@ -616,7 +644,7 @@ mod resp {
#[allow(clippy::result_large_err)]
pub trait MakeAxumError<E> {
fn make_axum_error(self, result: Result<Response, E>) -> Result<Response, Response>;
fn make_axum_error(self, result: Result<Response, E>) -> Response;
}
/// Get the status code from the error type if possible.
@@ -639,9 +667,9 @@ mod resp {
where
E: AsStatusCode + From<ServerFnError> + Serialize + DeserializeOwned + Display,
{
fn make_axum_error(self, result: Result<Response, E>) -> Result<Response, Response> {
fn make_axum_error(self, result: Result<Response, E>) -> Response {
match result {
Ok(res) => Ok(res),
Ok(res) => res,
Err(err) => {
let status_code = err.as_status_code();
let err = ErrorPayload {
@@ -656,19 +684,16 @@ mod resp {
HeaderValue::from_static("application/json"),
);
*resp.status_mut() = status_code;
Err(resp)
resp
}
}
}
}
impl<T> MakeAxumError<CapturedError> for &&ServerFnDecoder<Result<T, CapturedError>> {
fn make_axum_error(
self,
result: Result<Response, CapturedError>,
) -> Result<Response, Response> {
fn make_axum_error(self, result: Result<Response, CapturedError>) -> Response {
match result {
Ok(res) => Ok(res),
Ok(res) => res,
// Optimize the case where we have sole ownership of the error
Err(errr) if errr._strong_count() == 1 => {
@@ -721,19 +746,16 @@ mod resp {
HeaderValue::from_static("application/json"),
);
*resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
Err(resp)
resp
}
}
}
}
impl<T> MakeAxumError<anyhow::Error> for &&ServerFnDecoder<Result<T, anyhow::Error>> {
fn make_axum_error(
self,
result: Result<Response, anyhow::Error>,
) -> Result<Response, Response> {
fn make_axum_error(self, result: Result<Response, anyhow::Error>) -> Response {
match result {
Ok(res) => Ok(res),
Ok(res) => res,
Err(errr) => {
// The `WithHttpError` trait emits ServerFnErrors so we can downcast them here
// to create richer responses.
@@ -775,19 +797,16 @@ mod resp {
HeaderValue::from_static("application/json"),
);
*resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
Err(resp)
resp
}
}
}
}
impl<T> MakeAxumError<StatusCode> for &&ServerFnDecoder<Result<T, StatusCode>> {
fn make_axum_error(
self,
result: Result<Response, StatusCode>,
) -> Result<Response, Response> {
fn make_axum_error(self, result: Result<Response, StatusCode>) -> Response {
match result {
Ok(resp) => Ok(resp),
Ok(resp) => resp,
Err(status) => {
let body = serde_json::to_string(&ErrorPayload::<()> {
code: status.as_u16(),
@@ -801,19 +820,16 @@ mod resp {
HeaderValue::from_static("application/json"),
);
*resp.status_mut() = status;
Err(resp)
resp
}
}
}
}
impl<T> MakeAxumError<HttpError> for &ServerFnDecoder<Result<T, HttpError>> {
fn make_axum_error(
self,
result: Result<Response, HttpError>,
) -> Result<Response, Response> {
fn make_axum_error(self, result: Result<Response, HttpError>) -> Response {
match result {
Ok(resp) => Ok(resp),
Ok(resp) => resp,
Err(http_err) => {
let body = serde_json::to_string(&ErrorPayload::<()> {
code: http_err.status.as_u16(),
@@ -829,7 +845,7 @@ mod resp {
HeaderValue::from_static("application/json"),
);
*resp.status_mut() = http_err.status;
Err(resp)
resp
}
}
}