More fullstack fixes (#4737)
* allow `--server` only builds! * rollback example * capture `/:route` routes * fix: properly implement file uploads in forms * implement desktop form uploads * Performant serialization of form bytes * finish up file stuff * cleanups * add request headers to streaming context * remove some unwraps
This commit is contained in:
1 parent
fc01a002fd
commit
92792aded9
47 files changed
+1241
-703
No files matched your search
@@ -1,4 +1,5 @@
|
||||
use axum_core::response::IntoResponse;
|
||||
use futures_util::TryStreamExt;
|
||||
use http::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
@@ -91,6 +92,28 @@ impl ServerFnError {
|
||||
code: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new server error (status code 500) with a message and details.
|
||||
pub async fn from_axum_response(resp: axum_core::response::Response) -> Self {
|
||||
let status = resp.status();
|
||||
let message = resp
|
||||
.into_body()
|
||||
.into_data_stream()
|
||||
.try_fold(Vec::new(), |mut acc, chunk| async move {
|
||||
acc.extend_from_slice(&chunk);
|
||||
Ok(acc)
|
||||
})
|
||||
.await
|
||||
.ok()
|
||||
.and_then(|bytes| String::from_utf8(bytes).ok())
|
||||
.unwrap_or_else(|| status.canonical_reason().unwrap_or("").to_string());
|
||||
|
||||
ServerFnError::ServerError {
|
||||
message,
|
||||
code: Some(status.as_u16()),
|
||||
details: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<anyhow::Error> for ServerFnError {
|
||||
@@ -159,7 +182,64 @@ impl From<HttpError> for ServerFnError {
|
||||
|
||||
impl IntoResponse for ServerFnError {
|
||||
fn into_response(self) -> axum_core::response::Response {
|
||||
todo!()
|
||||
match self {
|
||||
Self::ServerError {
|
||||
message,
|
||||
code,
|
||||
details,
|
||||
} => {
|
||||
let status = code
|
||||
.and_then(|c| StatusCode::from_u16(c).ok())
|
||||
.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
let body = if let Some(details) = details {
|
||||
serde_json::json!({
|
||||
"error": message,
|
||||
"details": details,
|
||||
})
|
||||
} else {
|
||||
serde_json::json!({
|
||||
"error": message,
|
||||
})
|
||||
};
|
||||
let body = axum_core::body::Body::from(
|
||||
serde_json::to_string(&body)
|
||||
.unwrap_or_else(|_| "{\"error\":\"Internal Server Error\"}".to_string()),
|
||||
);
|
||||
axum_core::response::Response::builder()
|
||||
.status(status)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(body)
|
||||
.unwrap_or_else(|_| {
|
||||
axum_core::response::Response::builder()
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body(axum_core::body::Body::from(
|
||||
"{\"error\":\"Internal Server Error\"}",
|
||||
))
|
||||
.unwrap()
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
let status: StatusCode = self.clone().into();
|
||||
let body = axum_core::body::Body::from(
|
||||
serde_json::json!({
|
||||
"error": self.to_string(),
|
||||
})
|
||||
.to_string(),
|
||||
);
|
||||
axum_core::response::Response::builder()
|
||||
.status(status)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(body)
|
||||
.unwrap_or_else(|_| {
|
||||
axum_core::response::Response::builder()
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body(axum_core::body::Body::from(
|
||||
"{\"error\":\"Internal Server Error\"}",
|
||||
))
|
||||
.unwrap()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
use crate::ServerFnError;
|
||||
use axum_core::{extract::FromRequest, response::IntoResponse};
|
||||
use dioxus_core::try_consume_context;
|
||||
use dioxus_signals::{ReadableExt, Signal, WritableExt};
|
||||
use http::request::Parts;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
/// The status of the streaming response
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
@@ -13,23 +17,26 @@ pub enum StreamingStatus {
|
||||
}
|
||||
|
||||
/// The context dioxus fullstack provides for the status of streaming responses on the server
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct StreamingContext {
|
||||
current_status: Signal<StreamingStatus>,
|
||||
request_headers: Rc<RefCell<http::request::Parts>>,
|
||||
}
|
||||
|
||||
impl Default for StreamingContext {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
impl PartialEq for StreamingContext {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.current_status == other.current_status
|
||||
&& Rc::ptr_eq(&self.request_headers, &other.request_headers)
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamingContext {
|
||||
/// Create a new streaming context. You should not need to call this directly. Dioxus fullstack will
|
||||
/// provide this context for you.
|
||||
pub fn new() -> Self {
|
||||
pub fn new(parts: Parts) -> Self {
|
||||
Self {
|
||||
current_status: Signal::new(StreamingStatus::RenderingInitialChunk),
|
||||
request_headers: Rc::new(RefCell::new(parts)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +55,42 @@ impl StreamingContext {
|
||||
pub fn current_status(&self) -> StreamingStatus {
|
||||
*self.current_status.read()
|
||||
}
|
||||
|
||||
/// Access the http request parts mutably. This will allow you to modify headers and other parts of the request.
|
||||
pub fn parts_mut(&self) -> std::cell::RefMut<'_, http::request::Parts> {
|
||||
self.request_headers.borrow_mut()
|
||||
}
|
||||
|
||||
/// Extract an axum extractor from the current request. This will always use an empty body for the request,
|
||||
/// since it's assumed that rendering the app is done under a `GET` request.
|
||||
pub async fn extract<T: FromRequest<(), M>, M>() -> Result<T, ServerFnError> {
|
||||
let this = Self::current()
|
||||
.ok_or_else(|| ServerFnError::new("No StreamingContext found".to_string()))?;
|
||||
|
||||
let parts = this.request_headers.borrow_mut().clone();
|
||||
let request =
|
||||
axum_core::extract::Request::from_parts(parts, axum_core::body::Body::empty());
|
||||
match T::from_request(request, &()).await {
|
||||
Ok(res) => Ok(res),
|
||||
Err(err) => {
|
||||
let resp = err.into_response();
|
||||
Err(ServerFnError::from_axum_response(resp).await)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current `StreamingContext` if it exists. This will return `None` if called on the client
|
||||
/// or outside of a streaming response on the server.
|
||||
pub fn current() -> Option<Self> {
|
||||
if let Some(rt) = dioxus_core::Runtime::try_current() {
|
||||
let id = rt.try_current_scope_id()?;
|
||||
if let Some(ctx) = rt.consume_context::<StreamingContext>(id) {
|
||||
return Some(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Commit the initial chunk of the response. This will be called automatically if you are using the
|
||||
|
||||
Reference in new issue
Block a user