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:
Jonathan Kelley authored and GitHub committed 2025-10-03 18:26:19 -07:00
1 parent 8421c3aef0
commit f2b1c6d97f
424 files changed
+17284 -6624

No files matched your search

+7 -7
View File
@@ -59,7 +59,7 @@ impl DragData {
}
impl crate::HasFileData for DragData {
fn files(&self) -> Option<std::sync::Arc<dyn crate::file_data::FileEngine>> {
fn files(&self) -> Vec<FileData> {
self.inner.files()
}
}
@@ -111,8 +111,7 @@ impl PointerInteraction for DragData {
pub struct SerializedDragData {
pub mouse: crate::point_interaction::SerializedPointInteraction,
#[serde(default)]
files: Option<crate::file_data::SerializedFileEngine>,
pub files: Vec<SerializedFileData>,
}
#[cfg(feature = "serialize")]
@@ -120,7 +119,7 @@ impl SerializedDragData {
fn new(drag: &DragData) -> Self {
Self {
mouse: crate::point_interaction::SerializedPointInteraction::from(drag),
files: None,
files: vec![],
}
}
}
@@ -134,10 +133,11 @@ impl HasDragData for SerializedDragData {
#[cfg(feature = "serialize")]
impl crate::file_data::HasFileData for SerializedDragData {
fn files(&self) -> Option<std::sync::Arc<dyn crate::file_data::FileEngine>> {
fn files(&self) -> Vec<FileData> {
self.files
.as_ref()
.map(|files| std::sync::Arc::new(files.clone()) as _)
.iter()
.map(|f| FileData::new(f.clone()))
.collect()
}
}
+44 -38
View File
@@ -1,6 +1,10 @@
use crate::file_data::HasFileData;
use crate::FileData;
use std::{collections::HashMap, fmt::Debug, ops::Deref};
#[cfg(feature = "serialize")]
use crate::SerializedFileData;
use dioxus_core::Event;
pub type FormEvent = Event<FormData>;
@@ -105,7 +109,7 @@ impl FormData {
}
/// Get the files of the form event
pub fn files(&self) -> Option<std::sync::Arc<dyn crate::file_data::FileEngine>> {
pub fn files(&self) -> Vec<FileData> {
self.inner.files()
}
@@ -146,20 +150,24 @@ impl FormData {
where
T: serde::de::DeserializeOwned,
{
use serde::Serialize;
let values = &self.values();
fn convert_hashmap_to_json<K, V>(hashmap: &HashMap<K, V>) -> serde_json::Result<String>
where
K: Serialize + std::hash::Hash + Eq,
V: Serialize,
{
serde_json::to_string(hashmap)
let mut map = serde_json::Map::new();
for (key, value) in values {
if value.0.len() == 1 {
map.insert(key.clone(), serde_json::Value::String(value.0[0].clone()));
} else {
let arr = value
.0
.iter()
.cloned()
.map(serde_json::Value::String)
.collect();
map.insert(key.clone(), serde_json::Value::Array(arr));
}
}
let parsed_json =
convert_hashmap_to_json(&self.values()).expect("Failed to parse values to JSON");
serde_json::from_str(&parsed_json)
serde_json::from_value(serde_json::Value::Object(map))
}
}
@@ -177,7 +185,7 @@ pub struct SerializedFormData {
valid: bool,
#[serde(default)]
files: Option<crate::file_data::SerializedFileEngine>,
files: Vec<SerializedFileData>,
}
#[cfg(feature = "serialize")]
@@ -188,40 +196,37 @@ impl SerializedFormData {
value,
values,
valid: true,
files: None,
files: vec![],
}
}
/// Add files to the serialized form data object
pub fn with_files(mut self, files: crate::file_data::SerializedFileEngine) -> Self {
self.files = Some(files);
pub fn with_files(mut self, files: Vec<SerializedFileData>) -> Self {
self.files = files;
self
}
/// Create a new serialized form data object from a traditional form data object
pub async fn async_from(data: &FormData) -> Self {
Self {
pub async fn async_from(data: &FormData) -> Result<Self, dioxus_core::Error> {
Ok(Self {
value: data.value(),
values: data.values(),
valid: data.valid(),
files: {
match data.files() {
Some(files) => {
let mut resolved_files = HashMap::new();
for file in files.files() {
let bytes = files.read_file(&file).await;
resolved_files.insert(file, bytes.unwrap_or_default());
}
Some(crate::file_data::SerializedFileEngine {
files: resolved_files,
})
}
None => None,
let mut out = vec![];
for file in data.files() {
out.push(SerializedFileData {
name: file.name(),
size: file.size(),
content_type: file.content_type(),
last_modified: file.last_modified(),
path: file.pathbuf(),
contents: file.read_bytes().await?,
});
}
out
},
}
})
}
fn from_lossy(data: &FormData) -> Self {
@@ -229,7 +234,7 @@ impl SerializedFormData {
value: data.value(),
values: data.values(),
valid: data.valid(),
files: None,
files: vec![],
}
}
}
@@ -255,15 +260,16 @@ impl HasFormData for SerializedFormData {
#[cfg(feature = "serialize")]
impl HasFileData for SerializedFormData {
fn files(&self) -> Option<std::sync::Arc<dyn crate::FileEngine>> {
fn files(&self) -> Vec<FileData> {
self.files
.as_ref()
.map(|files| std::sync::Arc::new(files.clone()) as _)
.iter()
.map(|f| FileData::new(f.clone()))
.collect()
}
}
impl HasFileData for FormData {
fn files(&self) -> Option<std::sync::Arc<dyn crate::FileEngine>> {
fn files(&self) -> Vec<FileData> {
self.inner.files()
}
}
+3
View File
@@ -61,13 +61,16 @@ impl RenderedElementBacking for () {
}
/// The way that scrolling should be performed
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[doc(alias = "ScrollIntoViewOptions")]
pub enum ScrollBehavior {
/// Scroll to the element immediately
#[cfg_attr(feature = "serialize", serde(rename = "instant"))]
Instant,
/// Scroll to the element smoothly
#[default]
#[cfg_attr(feature = "serialize", serde(rename = "smooth"))]
Smooth,
}
+138 -41
View File
@@ -1,59 +1,156 @@
use std::{path::PathBuf, pin::Pin, prelude::rust_2024::Future};
use bytes::Bytes;
use futures_util::Stream;
#[derive(Clone)]
pub struct FileData {
inner: std::sync::Arc<dyn NativeFileData>,
}
impl FileData {
pub fn new(inner: impl NativeFileData + 'static) -> Self {
Self {
inner: std::sync::Arc::new(inner),
}
}
pub fn content_type(&self) -> Option<String> {
self.inner.content_type()
}
pub fn name(&self) -> String {
self.inner.name()
}
pub fn size(&self) -> u64 {
self.inner.size()
}
pub fn last_modified(&self) -> u64 {
self.inner.last_modified()
}
pub async fn read_bytes(&self) -> Result<Bytes, dioxus_core::Error> {
self.inner.read_bytes().await
}
pub async fn read_string(&self) -> Result<String, dioxus_core::Error> {
self.inner.read_string().await
}
pub fn byte_stream(
&self,
) -> Pin<Box<dyn Stream<Item = Result<Bytes, dioxus_core::Error>> + Send + 'static>> {
self.inner.byte_stream()
}
pub fn inner(&self) -> &dyn std::any::Any {
self.inner.inner()
}
pub fn pathbuf(&self) -> PathBuf {
self.inner.path()
}
}
pub trait NativeFileData: Send + Sync {
fn name(&self) -> String;
fn size(&self) -> u64;
fn last_modified(&self) -> u64;
fn path(&self) -> PathBuf;
fn content_type(&self) -> Option<String>;
fn read_bytes(
&self,
) -> Pin<Box<dyn Future<Output = Result<Bytes, dioxus_core::Error>> + 'static>>;
fn byte_stream(
&self,
) -> Pin<Box<dyn futures_util::Stream<Item = Result<Bytes, dioxus_core::Error>> + 'static + Send>>;
fn read_string(
&self,
) -> Pin<Box<dyn Future<Output = Result<String, dioxus_core::Error>> + 'static>>;
fn inner(&self) -> &dyn std::any::Any;
}
impl std::fmt::Debug for FileData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FileData")
.field("name", &self.inner.name())
.field("size", &self.inner.size())
.field("last_modified", &self.inner.last_modified())
.finish()
}
}
impl std::cmp::PartialEq for FileData {
fn eq(&self, other: &Self) -> bool {
self.inner.name() == other.inner.name()
&& self.inner.size() == other.inner.size()
&& self.inner.last_modified() == other.inner.last_modified()
}
}
pub trait HasFileData: std::any::Any {
fn files(&self) -> Option<std::sync::Arc<dyn FileEngine>> {
None
}
fn files(&self) -> Vec<FileData>;
}
/// A serializable representation of file data\
#[cfg(feature = "serialize")]
/// A file engine that serializes files to bytes
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]
pub struct SerializedFileEngine {
pub files: std::collections::HashMap<String, Vec<u8>>,
pub struct SerializedFileData {
pub name: String,
pub path: PathBuf,
pub size: u64,
pub last_modified: u64,
pub content_type: Option<String>,
pub contents: bytes::Bytes,
}
#[cfg(feature = "serialize")]
#[async_trait::async_trait(?Send)]
impl FileEngine for SerializedFileEngine {
fn files(&self) -> Vec<String> {
self.files.keys().cloned().collect()
impl NativeFileData for SerializedFileData {
fn name(&self) -> String {
self.name.clone()
}
async fn file_size(&self, file: &str) -> Option<u64> {
let file = self.files.get(file)?;
Some(file.len() as u64)
fn size(&self) -> u64 {
self.size
}
async fn read_file(&self, file: &str) -> Option<Vec<u8>> {
self.files.get(file).cloned()
fn last_modified(&self) -> u64 {
self.last_modified
}
async fn read_file_to_string(&self, file: &str) -> Option<String> {
self.read_file(file)
.await
.map(|bytes| String::from_utf8_lossy(&bytes).to_string())
fn read_bytes(
&self,
) -> Pin<Box<dyn Future<Output = Result<Bytes, dioxus_core::Error>> + 'static>> {
let contents = self.contents.clone();
Box::pin(async move { Ok(contents) })
}
async fn get_native_file(&self, file: &str) -> Option<Box<dyn std::any::Any>> {
self.read_file(file)
.await
.map(|val| Box::new(val) as Box<dyn std::any::Any>)
fn read_string(
&self,
) -> Pin<Box<dyn Future<Output = Result<String, dioxus_core::Error>> + 'static>> {
let contents = self.contents.clone();
Box::pin(async move { Ok(String::from_utf8(contents.to_vec())?) })
}
fn byte_stream(
&self,
) -> Pin<Box<dyn futures_util::Stream<Item = Result<Bytes, dioxus_core::Error>> + 'static + Send>>
{
let contents = self.contents.clone();
Box::pin(futures_util::stream::once(async move { Ok(contents) }))
}
fn inner(&self) -> &dyn std::any::Any {
self
}
fn path(&self) -> PathBuf {
self.path.clone()
}
fn content_type(&self) -> Option<String> {
self.content_type.clone()
}
}
#[async_trait::async_trait(?Send)]
pub trait FileEngine {
// get a list of file names
fn files(&self) -> Vec<String>;
// get the size of a file
async fn file_size(&self, file: &str) -> Option<u64>;
// read a file to bytes
async fn read_file(&self, file: &str) -> Option<Vec<u8>>;
// read a file to string
async fn read_file_to_string(&self, file: &str) -> Option<String>;
// returns a file in platform's native representation
async fn get_native_file(&self, file: &str) -> Option<Box<dyn std::any::Any>>;
}
+2
View File
@@ -30,6 +30,8 @@ pub mod input_data;
pub mod point_interaction;
mod render_template;
pub use bytes;
#[cfg(feature = "serialize")]
mod transit;
+1 -1
View File
@@ -37,7 +37,7 @@ impl<'de> Deserialize<'de> for HtmlEvent {
// in debug mode let's try and be helpful as to why the deserialization failed
let data = deserialize_raw(&name, &data).map_err(|e| {
serde::de::Error::custom(format!(
"Failed to deserialize event data for event {}: {:#?}\n'{:#?}'",
"Failed to deserialize event data for event {}: {}\n'{:#?}'",
name, e, data,
))
})?;