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
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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>>;
|
||||
}
|
||||
@@ -30,6 +30,8 @@ pub mod input_data;
|
||||
pub mod point_interaction;
|
||||
mod render_template;
|
||||
|
||||
pub use bytes;
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
mod transit;
|
||||
|
||||
|
||||
@@ -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,
|
||||
))
|
||||
})?;
|
||||
|
||||
Reference in new issue
Block a user