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
+241
-198
@@ -1,77 +1,16 @@
|
||||
use crate::file_data::HasFileData;
|
||||
use crate::FileData;
|
||||
use std::{collections::HashMap, fmt::Debug, ops::Deref};
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
use crate::SerializedFileData;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use dioxus_core::Event;
|
||||
|
||||
pub type FormEvent = Event<FormData>;
|
||||
|
||||
/// A form value that may either be a list of values or a single value
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct FormValue(pub Vec<String>);
|
||||
|
||||
impl Deref for FormValue {
|
||||
type Target = [String];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl FormValue {
|
||||
/// Convenient way to represent Value as slice
|
||||
pub fn as_slice(&self) -> &[String] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Return the first value, panicking if there are none
|
||||
pub fn as_value(&self) -> String {
|
||||
self.0.first().unwrap().clone()
|
||||
}
|
||||
|
||||
/// Convert into [`Vec<String>`]
|
||||
pub fn to_vec(self) -> Vec<String> {
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<str> for FormValue {
|
||||
fn eq(&self, other: &str) -> bool {
|
||||
self.0.len() == 1 && self.0.first().map(|s| s.as_str()) == Some(other)
|
||||
}
|
||||
}
|
||||
|
||||
/* DOMEvent: Send + SyncTarget relatedTarget */
|
||||
pub struct FormData {
|
||||
inner: Box<dyn HasFormData>,
|
||||
}
|
||||
|
||||
impl<E: HasFormData> From<E> for FormData {
|
||||
fn from(e: E) -> Self {
|
||||
Self { inner: Box::new(e) }
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for FormData {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.value() == other.value() && self.values() == other.values()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for FormData {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("FormEvent")
|
||||
.field("value", &self.value())
|
||||
.field("values", &self.values())
|
||||
.field("valid", &self.valid())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl FormData {
|
||||
/// Create a new form event
|
||||
pub fn new(event: impl HasFormData + 'static) -> Self {
|
||||
@@ -104,10 +43,25 @@ impl FormData {
|
||||
/// Collect all the named form values from the containing form.
|
||||
///
|
||||
/// Every input must be named!
|
||||
pub fn values(&self) -> HashMap<String, FormValue> {
|
||||
pub fn values(&self) -> Vec<(String, FormValue)> {
|
||||
self.inner.values()
|
||||
}
|
||||
|
||||
/// Get the first value with the given name
|
||||
pub fn get_first(&self, name: &str) -> Option<FormValue> {
|
||||
self.values()
|
||||
.into_iter()
|
||||
.find_map(|(k, v)| if k == name { Some(v) } else { None })
|
||||
}
|
||||
|
||||
/// Get all values with the given name
|
||||
pub fn get(&self, name: &str) -> Vec<FormValue> {
|
||||
self.values()
|
||||
.into_iter()
|
||||
.filter_map(|(k, v)| if k == name { Some(v) } else { None })
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get the files of the form event
|
||||
pub fn files(&self) -> Vec<FileData> {
|
||||
self.inner.files()
|
||||
@@ -125,146 +79,69 @@ impl FormData {
|
||||
}
|
||||
}
|
||||
|
||||
/// An object that has all the data for a form event
|
||||
pub trait HasFormData: HasFileData + std::any::Any {
|
||||
fn value(&self) -> String {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn valid(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn values(&self) -> HashMap<String, FormValue> {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// return self as Any
|
||||
fn as_any(&self) -> &dyn std::any::Any;
|
||||
}
|
||||
|
||||
impl FormData {
|
||||
#[cfg(feature = "serialize")]
|
||||
/// Parse the values into a struct with one field per value
|
||||
#[cfg(feature = "serialize")]
|
||||
pub fn parsed_values<T>(&self) -> Result<T, serde_json::Error>
|
||||
where
|
||||
T: serde::de::DeserializeOwned,
|
||||
{
|
||||
use crate::SerializedFileData;
|
||||
|
||||
let values = &self.values();
|
||||
|
||||
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 entry = map
|
||||
.entry(key.clone())
|
||||
.or_insert_with(|| serde_json::Value::Array(Vec::new()));
|
||||
|
||||
match value {
|
||||
FormValue::Text(text) => {
|
||||
entry
|
||||
.as_array_mut()
|
||||
.expect("entry should be an array")
|
||||
.push(serde_json::Value::String(text.clone()));
|
||||
}
|
||||
// we create the serialized variant with no bytes
|
||||
// SerializedFileData, if given a real path, will read the bytes from disk (synchronously)
|
||||
FormValue::File(Some(file_data)) => {
|
||||
let serialized = SerializedFileData {
|
||||
path: file_data.path().to_owned(),
|
||||
size: file_data.size(),
|
||||
last_modified: file_data.last_modified(),
|
||||
content_type: file_data.content_type(),
|
||||
contents: None,
|
||||
};
|
||||
entry
|
||||
.as_array_mut()
|
||||
.expect("entry should be an array")
|
||||
.push(serde_json::to_value(&serialized).unwrap_or(serde_json::Value::Null));
|
||||
}
|
||||
FormValue::File(None) => {
|
||||
entry
|
||||
.as_array_mut()
|
||||
.expect("entry should be an array")
|
||||
.push(
|
||||
serde_json::to_value(SerializedFileData::empty())
|
||||
.unwrap_or(serde_json::Value::Null),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
serde_json::from_value(serde_json::Value::Object(map))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
/// A serialized form data object
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct SerializedFormData {
|
||||
#[serde(default)]
|
||||
value: String,
|
||||
|
||||
#[serde(default)]
|
||||
values: HashMap<String, FormValue>,
|
||||
|
||||
#[serde(default)]
|
||||
valid: bool,
|
||||
|
||||
#[serde(default)]
|
||||
files: Vec<SerializedFileData>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
impl SerializedFormData {
|
||||
/// Create a new serialized form data object
|
||||
pub fn new(value: String, values: HashMap<String, FormValue>) -> Self {
|
||||
Self {
|
||||
value,
|
||||
values,
|
||||
valid: true,
|
||||
files: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Add files to the serialized form data object
|
||||
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) -> Result<Self, dioxus_core::Error> {
|
||||
Ok(Self {
|
||||
value: data.value(),
|
||||
values: data.values(),
|
||||
valid: data.valid(),
|
||||
files: {
|
||||
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?,
|
||||
});
|
||||
// Go through the map and convert single-element arrays to just the element
|
||||
let map = map
|
||||
.into_iter()
|
||||
.map(|(k, v)| match v {
|
||||
serde_json::Value::Array(arr) if arr.len() == 1 => {
|
||||
(k, arr.into_iter().next().unwrap())
|
||||
}
|
||||
out
|
||||
},
|
||||
})
|
||||
}
|
||||
_ => (k, v),
|
||||
})
|
||||
.collect::<serde_json::Map<String, serde_json::Value>>();
|
||||
|
||||
fn from_lossy(data: &FormData) -> Self {
|
||||
Self {
|
||||
value: data.value(),
|
||||
values: data.values(),
|
||||
valid: data.valid(),
|
||||
files: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
impl HasFormData for SerializedFormData {
|
||||
fn value(&self) -> String {
|
||||
self.value.clone()
|
||||
}
|
||||
|
||||
fn values(&self) -> HashMap<String, FormValue> {
|
||||
self.values.clone()
|
||||
}
|
||||
|
||||
fn valid(&self) -> bool {
|
||||
self.valid
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
impl HasFileData for SerializedFormData {
|
||||
fn files(&self) -> Vec<FileData> {
|
||||
self.files
|
||||
.iter()
|
||||
.map(|f| FileData::new(f.clone()))
|
||||
.collect()
|
||||
serde_json::from_value(serde_json::Value::Object(map))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,20 +151,186 @@ impl HasFileData for FormData {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
impl serde::Serialize for FormData {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
SerializedFormData::from_lossy(self).serialize(serializer)
|
||||
impl<E: HasFormData> From<E> for FormData {
|
||||
fn from(e: E) -> Self {
|
||||
Self { inner: Box::new(e) }
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for FormData {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.value() == other.value() && self.values() == other.values()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for FormData {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("FormEvent")
|
||||
.field("value", &self.value())
|
||||
.field("values", &self.values())
|
||||
.field("valid", &self.valid())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// A value in a form, either text or a file
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum FormValue {
|
||||
Text(String),
|
||||
File(Option<FileData>),
|
||||
}
|
||||
|
||||
impl PartialEq<str> for FormValue {
|
||||
fn eq(&self, other: &str) -> bool {
|
||||
match self {
|
||||
FormValue::Text(s) => s == other,
|
||||
FormValue::File(_f) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<&str> for FormValue {
|
||||
fn eq(&self, other: &&str) -> bool {
|
||||
match self {
|
||||
FormValue::Text(s) => s == other,
|
||||
FormValue::File(_f) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An object that has all the data for a form event
|
||||
pub trait HasFormData: HasFileData + std::any::Any {
|
||||
fn value(&self) -> String;
|
||||
|
||||
fn valid(&self) -> bool;
|
||||
|
||||
fn values(&self) -> Vec<(String, FormValue)>;
|
||||
|
||||
/// return self as Any
|
||||
fn as_any(&self) -> &dyn std::any::Any;
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
impl<'de> serde::Deserialize<'de> for FormData {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let data = SerializedFormData::deserialize(deserializer)?;
|
||||
Ok(Self {
|
||||
inner: Box::new(data),
|
||||
})
|
||||
pub use serialize::*;
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
mod serialize {
|
||||
use crate::SerializedFileData;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// A serialized form data object
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct SerializedFormData {
|
||||
#[serde(default)]
|
||||
pub value: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub values: Vec<SerializedFormObject>,
|
||||
|
||||
#[serde(default)]
|
||||
pub valid: bool,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct SerializedFormObject {
|
||||
pub key: String,
|
||||
pub text: Option<String>,
|
||||
pub file: Option<SerializedFileData>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
impl SerializedFormData {
|
||||
/// Create a new serialized form data object
|
||||
pub fn new(value: String, values: Vec<SerializedFormObject>) -> Self {
|
||||
Self {
|
||||
value,
|
||||
values,
|
||||
valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a serialized form data object from a form data object
|
||||
fn from_form_lossy(data: &FormData) -> Self {
|
||||
if let Some(data) = data.downcast::<SerializedFormData>() {
|
||||
return data.clone();
|
||||
}
|
||||
|
||||
let values = data
|
||||
.values()
|
||||
.iter()
|
||||
.map(|(key, value)| match value {
|
||||
FormValue::Text(s) => SerializedFormObject {
|
||||
key: key.clone(),
|
||||
text: Some(s.to_string()),
|
||||
file: None,
|
||||
},
|
||||
FormValue::File(f) => SerializedFormObject {
|
||||
key: key.clone(),
|
||||
text: None,
|
||||
file: if let Some(f) = f {
|
||||
Some(SerializedFileData {
|
||||
path: f.path(),
|
||||
size: f.size(),
|
||||
last_modified: f.last_modified(),
|
||||
content_type: f.content_type(),
|
||||
contents: None,
|
||||
})
|
||||
} else {
|
||||
Some(SerializedFileData::empty())
|
||||
},
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
values,
|
||||
value: data.value(),
|
||||
valid: data.valid(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HasFormData for SerializedFormData {
|
||||
fn value(&self) -> String {
|
||||
self.value.clone()
|
||||
}
|
||||
|
||||
fn values(&self) -> Vec<(String, FormValue)> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn valid(&self) -> bool {
|
||||
self.valid
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl HasFileData for SerializedFormData {
|
||||
fn files(&self) -> Vec<FileData> {
|
||||
self.values
|
||||
.iter()
|
||||
.filter_map(|v| v.file.as_ref().map(|f| FileData::new(f.clone())))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for FormData {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
SerializedFormData::from_form_lossy(self).serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for FormData {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let data = SerializedFormData::deserialize(deserializer)?;
|
||||
Ok(Self {
|
||||
inner: Box::new(data),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+134
-60
@@ -1,7 +1,6 @@
|
||||
use std::{path::PathBuf, pin::Pin, prelude::rust_2024::Future};
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures_util::Stream;
|
||||
use std::{path::PathBuf, pin::Pin, prelude::rust_2024::Future};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FileData {
|
||||
@@ -49,11 +48,20 @@ impl FileData {
|
||||
self.inner.inner()
|
||||
}
|
||||
|
||||
pub fn pathbuf(&self) -> PathBuf {
|
||||
pub fn path(&self) -> PathBuf {
|
||||
self.inner.path()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for FileData {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.name() == other.name()
|
||||
&& self.size() == other.size()
|
||||
&& self.last_modified() == other.last_modified()
|
||||
&& self.path() == other.path()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait NativeFileData: Send + Sync {
|
||||
fn name(&self) -> String;
|
||||
fn size(&self) -> u64;
|
||||
@@ -82,75 +90,141 @@ impl std::fmt::Debug for FileData {
|
||||
}
|
||||
}
|
||||
|
||||
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) -> Vec<FileData>;
|
||||
}
|
||||
|
||||
/// A serializable representation of file data\
|
||||
#[cfg(feature = "serialize")]
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]
|
||||
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,
|
||||
}
|
||||
pub use serialize::*;
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
impl NativeFileData for SerializedFileData {
|
||||
fn name(&self) -> String {
|
||||
self.name.clone()
|
||||
mod serialize {
|
||||
use super::*;
|
||||
|
||||
/// A serializable representation of file data
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct SerializedFileData {
|
||||
pub path: PathBuf,
|
||||
pub size: u64,
|
||||
pub last_modified: u64,
|
||||
pub content_type: Option<String>,
|
||||
pub contents: Option<bytes::Bytes>,
|
||||
}
|
||||
|
||||
fn size(&self) -> u64 {
|
||||
self.size
|
||||
impl SerializedFileData {
|
||||
/// Create a new empty serialized file data object
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
path: PathBuf::new(),
|
||||
size: 0,
|
||||
last_modified: 0,
|
||||
content_type: None,
|
||||
contents: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn last_modified(&self) -> u64 {
|
||||
self.last_modified
|
||||
impl NativeFileData for SerializedFileData {
|
||||
fn name(&self) -> String {
|
||||
self.path()
|
||||
.file_name()
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
}
|
||||
|
||||
fn size(&self) -> u64 {
|
||||
self.size
|
||||
}
|
||||
|
||||
fn last_modified(&self) -> u64 {
|
||||
self.last_modified
|
||||
}
|
||||
|
||||
fn read_bytes(
|
||||
&self,
|
||||
) -> Pin<Box<dyn Future<Output = Result<Bytes, dioxus_core::Error>> + 'static>> {
|
||||
let contents = self.contents.clone();
|
||||
let path = self.path.clone();
|
||||
|
||||
Box::pin(async move {
|
||||
if let Some(contents) = contents {
|
||||
return Ok(contents);
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if path.exists() {
|
||||
return Ok(std::fs::read(path).map(Bytes::from)?);
|
||||
}
|
||||
|
||||
Err(dioxus_core::Error::msg("File contents not available"))
|
||||
})
|
||||
}
|
||||
|
||||
fn read_string(
|
||||
&self,
|
||||
) -> Pin<Box<dyn Future<Output = Result<String, dioxus_core::Error>> + 'static>> {
|
||||
let contents = self.contents.clone();
|
||||
let path = self.path.clone();
|
||||
|
||||
Box::pin(async move {
|
||||
if let Some(contents) = contents {
|
||||
return Ok(String::from_utf8(contents.to_vec())?);
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if path.exists() {
|
||||
return Ok(std::fs::read_to_string(path)?);
|
||||
}
|
||||
|
||||
Err(dioxus_core::Error::msg("File contents not available"))
|
||||
})
|
||||
}
|
||||
|
||||
fn byte_stream(
|
||||
&self,
|
||||
) -> Pin<
|
||||
Box<
|
||||
dyn futures_util::Stream<Item = Result<Bytes, dioxus_core::Error>> + 'static + Send,
|
||||
>,
|
||||
> {
|
||||
let contents = self.contents.clone();
|
||||
let path = self.path.clone();
|
||||
|
||||
Box::pin(futures_util::stream::once(async move {
|
||||
if let Some(contents) = contents {
|
||||
return Ok(contents);
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if path.exists() {
|
||||
return Ok(std::fs::read(path).map(Bytes::from)?);
|
||||
}
|
||||
|
||||
Err(dioxus_core::Error::msg("File contents not available"))
|
||||
}))
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
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) })
|
||||
}
|
||||
|
||||
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()
|
||||
impl<'de> serde::Deserialize<'de> for FileData {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let sfd = SerializedFileData::deserialize(deserializer)?;
|
||||
Ok(FileData::new(sfd))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user