dioxus_fullstack/
request.rs

1use dioxus_fullstack_core::{RequestError, ServerFnError};
2#[cfg(feature = "server")]
3use headers::Header;
4use http::response::Parts;
5use std::{future::Future, pin::Pin};
6
7use crate::{ClientRequest, ClientResponse};
8
9/// The `IntoRequest` trait allows types to be used as the body of a request to a HTTP endpoint or server function.
10///
11/// `IntoRequest` allows for types handle the calling of `ClientRequest::send` where the result is then
12/// passed to `FromResponse` to decode the response.
13///
14/// You can think of the `IntoRequest` and `FromResponse` traits are "inverse" traits of the axum
15/// `FromRequest` and `IntoResponse` traits. Just like a type can be decoded from a request via `FromRequest`,
16/// a type can be encoded into a request via `IntoRequest`.
17///
18/// ## Generic State
19///
20/// `IntoRequest` is generic over the response type `R` which defaults to `ClientResponse`. The default
21/// `ClientResponse` is the base response type that internally wraps `reqwest::Response`.
22///
23/// However, some responses might need state from the initial request to properly decode the response.
24/// Most state can be extended via the `.extension()` method on `ClientRequest`. In some cases, like
25/// websockets, the response needs to retain an initial connection from the request. Here, you can use
26///  the `R` generic to specify a concrete response type. The resulting type that implements `FromResponse`
27/// must also be generic over the same `R` type.
28pub trait IntoRequest<R = ClientResponse>: Sized {
29    fn into_request(
30        self,
31        req: ClientRequest,
32    ) -> impl Future<Output = Result<R, RequestError>> + 'static;
33}
34
35impl<A, R> IntoRequest<R> for (A,)
36where
37    A: IntoRequest<R> + 'static + Send,
38{
39    fn into_request(
40        self,
41        req: ClientRequest,
42    ) -> impl Future<Output = Result<R, RequestError>> + 'static {
43        A::into_request(self.0, req)
44    }
45}
46
47pub trait FromResponse<R = ClientResponse>: Sized {
48    fn from_response(res: R) -> impl Future<Output = Result<Self, ServerFnError>>;
49}
50
51impl<A> FromResponse for A
52where
53    A: FromResponseParts,
54{
55    fn from_response(res: ClientResponse) -> impl Future<Output = Result<Self, ServerFnError>> {
56        async move {
57            let (parts, _body) = res.into_parts();
58            let mut parts = parts;
59            A::from_response_parts(&mut parts)
60        }
61    }
62}
63
64impl<A, B> FromResponse for (A, B)
65where
66    A: FromResponseParts,
67    B: FromResponse,
68{
69    fn from_response(res: ClientResponse) -> impl Future<Output = Result<Self, ServerFnError>> {
70        async move {
71            let mut parts = res.make_parts();
72            let a = A::from_response_parts(&mut parts)?;
73            let b = B::from_response(res).await?;
74            Ok((a, b))
75        }
76    }
77}
78
79impl<A, B, C> FromResponse for (A, B, C)
80where
81    A: FromResponseParts,
82    B: FromResponseParts,
83    C: FromResponse,
84{
85    fn from_response(res: ClientResponse) -> impl Future<Output = Result<Self, ServerFnError>> {
86        async move {
87            let mut parts = res.make_parts();
88            let a = A::from_response_parts(&mut parts)?;
89            let b = B::from_response_parts(&mut parts)?;
90            let c = C::from_response(res).await?;
91            Ok((a, b, c))
92        }
93    }
94}
95
96pub trait FromResponseParts
97where
98    Self: Sized,
99{
100    fn from_response_parts(parts: &mut Parts) -> Result<Self, ServerFnError>;
101}
102
103#[cfg(feature = "server")]
104impl<T: Header> FromResponseParts for axum_extra::TypedHeader<T> {
105    fn from_response_parts(parts: &mut Parts) -> Result<Self, ServerFnError> {
106        use headers::HeaderMapExt;
107
108        let t = parts
109            .headers
110            .typed_get::<T>()
111            .ok_or_else(|| ServerFnError::Serialization("Invalid header value".into()))?;
112
113        Ok(axum_extra::TypedHeader(t))
114    }
115}
116
117/*
118todo: make the serverfns return ServerFnRequest which lets us control the future better
119*/
120#[pin_project::pin_project]
121#[must_use = "Requests do nothing unless you `.await` them"]
122pub struct ServerFnRequest<Output> {
123    _phantom: std::marker::PhantomData<Output>,
124    #[pin]
125    fut: Pin<Box<dyn Future<Output = Output> + Send>>,
126}
127
128impl<O> ServerFnRequest<O> {
129    pub fn new(res: impl Future<Output = O> + Send + 'static) -> Self {
130        ServerFnRequest {
131            _phantom: std::marker::PhantomData,
132            fut: Box::pin(res),
133        }
134    }
135}
136
137impl<T, E> std::future::Future for ServerFnRequest<Result<T, E>> {
138    type Output = Result<T, E>;
139
140    fn poll(
141        self: std::pin::Pin<&mut Self>,
142        cx: &mut std::task::Context<'_>,
143    ) -> std::task::Poll<Self::Output> {
144        self.project().fut.poll(cx)
145    }
146}
147
148#[doc(hidden)]
149#[diagnostic::on_unimplemented(
150    message = "The return type of a server function must be `Result<T, E>`",
151    note = "`T` is either `impl IntoResponse` *or* `impl Serialize`",
152    note = "`E` is either `From<ServerFnError> + Serialize`, `dioxus::CapturedError` or `StatusCode`."
153)]
154pub trait AssertIsResult {}
155impl<T, E> AssertIsResult for Result<T, E> {}
156
157#[doc(hidden)]
158pub fn assert_is_result<T: AssertIsResult>() {}
159
160#[diagnostic::on_unimplemented(message = r#"❌ Invalid Arguments to ServerFn ❌
161
162The arguments to the server function must be either:
163
164- a single `impl FromRequest + IntoRequest` argument
165- or multiple `DeserializeOwned` arguments.
166
167Did you forget to implement `IntoRequest` or `Deserialize` for one of the arguments?
168
169`IntoRequest` is a trait that allows payloads to be sent to the server function.
170
171> See https://dioxuslabs.com/learn/0.7/essentials/fullstack/server_functions for more details.
172
173"#)]
174pub trait AssertCanEncode {}
175
176pub struct CantEncode;
177
178pub struct EncodeIsVerified;
179impl AssertCanEncode for EncodeIsVerified {}
180
181#[diagnostic::on_unimplemented(message = r#"❌ Invalid return type from ServerFn ❌
182
183The arguments to the server function must be either:
184
185- a single `impl FromResponse` return type
186- a single `impl Serialize + DeserializedOwned` return type
187
188Did you forget to implement `FromResponse` or `DeserializeOwned` for one of the arguments?
189
190`FromResponse` is a trait that allows payloads to be decoded from the server function response.
191
192> See https://dioxuslabs.com/learn/0.7/essentials/fullstack/server_functions for more details.
193
194"#)]
195pub trait AssertCanDecode {}
196pub struct CantDecode;
197pub struct DecodeIsVerified;
198impl AssertCanDecode for DecodeIsVerified {}
199
200#[doc(hidden)]
201pub fn assert_can_encode(_t: impl AssertCanEncode) {}
202
203#[doc(hidden)]
204pub fn assert_can_decode(_t: impl AssertCanDecode) {}