mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-27 15:44:42 -05:00
Compare commits
27 Commits
pause-effe
...
result-ali
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae9f090238 | ||
|
|
287fc47163 | ||
|
|
8f74a6d8a0 | ||
|
|
597175a54b | ||
|
|
ede25b9e3d | ||
|
|
8f636e354a | ||
|
|
7da64f22c4 | ||
|
|
0073ae7d8a | ||
|
|
8465716a19 | ||
|
|
0e24b2e63f | ||
|
|
c64d205984 | ||
|
|
f17cb98eb0 | ||
|
|
30f3e82664 | ||
|
|
152d5a5c92 | ||
|
|
669e1ba7fa | ||
|
|
2ad6a086f9 | ||
|
|
32e58d6b66 | ||
|
|
a107443104 | ||
|
|
c859b07901 | ||
|
|
a9868bea2b | ||
|
|
7183c2b993 | ||
|
|
7a03621db1 | ||
|
|
2b589fa61f | ||
|
|
35e6f17930 | ||
|
|
d1513a4a0b | ||
|
|
aa27b9e474 | ||
|
|
cfe925d58f |
552
Cargo.lock
generated
552
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
28
Cargo.toml
28
Cargo.toml
@@ -40,7 +40,7 @@ members = [
|
||||
exclude = ["benchmarks", "examples", "projects"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.7.4"
|
||||
version = "0.8.0-alpha"
|
||||
edition = "2021"
|
||||
rust-version = "1.76"
|
||||
|
||||
@@ -50,25 +50,25 @@ any_spawner = { path = "./any_spawner/", version = "0.2.0" }
|
||||
const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1" }
|
||||
either_of = { path = "./either_of/", version = "0.1.0" }
|
||||
hydration_context = { path = "./hydration_context", version = "0.2.0" }
|
||||
leptos = { path = "./leptos", version = "0.7.4" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.7.4" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.7.4" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.4" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.4" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.7.4" }
|
||||
leptos_router = { path = "./router", version = "0.7.4" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.7.4" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.7.4" }
|
||||
leptos_meta = { path = "./meta", version = "0.7.4" }
|
||||
leptos = { path = "./leptos", version = "0.8.0-alpha" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.8.0-alpha" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.8.0-alpha" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.8.0-alpha" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.8.0-alpha" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.8.0-alpha" }
|
||||
leptos_router = { path = "./router", version = "0.8.0-alpha" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.8.0-alpha" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.8.0-alpha" }
|
||||
leptos_meta = { path = "./meta", version = "0.8.0-alpha" }
|
||||
next_tuple = { path = "./next_tuple", version = "0.1.0" }
|
||||
oco_ref = { path = "./oco", version = "0.2.0" }
|
||||
or_poisoned = { path = "./or_poisoned", version = "0.1.0" }
|
||||
reactive_graph = { path = "./reactive_graph", version = "0.1.4" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.1.3" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.0" }
|
||||
server_fn = { path = "./server_fn", version = "0.7.4" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.7.4" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.4" }
|
||||
server_fn = { path = "./server_fn", version = "0.8.0-alpha" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.8.0-alpha" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.8.0-alpha" }
|
||||
tachys = { path = "./tachys", version = "0.1.4" }
|
||||
|
||||
[profile.release]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "throw_error"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -17,11 +17,6 @@ use std::{
|
||||
|
||||
/* Wrapper Types */
|
||||
|
||||
/// This is a result type into which any error can be converted.
|
||||
///
|
||||
/// Results are stored as [`Error`].
|
||||
pub type Result<T, E = Error> = core::result::Result<T, E>;
|
||||
|
||||
/// A generic wrapper for any error.
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(transparent)]
|
||||
|
||||
@@ -7,7 +7,7 @@ edition = "2021"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
axum = { version = "0.8.1", optional = true }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
console_log = "1.0"
|
||||
gloo-utils = "0.2.0"
|
||||
@@ -20,18 +20,27 @@ leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
leptos_router = { path = "../../router" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1.39", features = [ "rt-multi-thread", "macros", "time" ], optional = true }
|
||||
tokio = { version = "1.39", features = [
|
||||
"rt-multi-thread",
|
||||
"macros",
|
||||
"time",
|
||||
], optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
wasm-bindgen = "0.2.92"
|
||||
web-sys = { version = "0.3.69", features = [ "AddEventListenerOptions", "Document", "Element", "Event", "EventListener", "EventTarget", "Performance", "Window" ], optional = true }
|
||||
web-sys = { version = "0.3.69", features = [
|
||||
"AddEventListenerOptions",
|
||||
"Document",
|
||||
"Element",
|
||||
"Event",
|
||||
"EventListener",
|
||||
"EventTarget",
|
||||
"Performance",
|
||||
"Window",
|
||||
], optional = true }
|
||||
|
||||
[features]
|
||||
hydrate = [
|
||||
"leptos/hydrate",
|
||||
"dep:js-sys",
|
||||
"dep:web-sys",
|
||||
]
|
||||
hydrate = ["leptos/hydrate", "dep:js-sys", "dep:web-sys"]
|
||||
ssr = [
|
||||
"dep:axum",
|
||||
"dep:http-body-util",
|
||||
|
||||
@@ -19,7 +19,7 @@ async fn clear() {
|
||||
// note that we start at the initial value of 10
|
||||
let _dispose = mount_to(
|
||||
test_wrapper.clone().unchecked_into(),
|
||||
|| view! { <SimpleCounter initial_value=10 step=1/> },
|
||||
|| view! { <SimpleCounter initial_value=10 step=1 /> },
|
||||
);
|
||||
|
||||
// now we extract the buttons by iterating over the DOM
|
||||
@@ -59,9 +59,9 @@ async fn clear() {
|
||||
// .into_view() here is just a convenient way of specifying "use the regular DOM renderer"
|
||||
.into_view()
|
||||
// views are lazy -- they describe a DOM tree but don't create it yet
|
||||
// calling .build() will actually build the DOM elements
|
||||
.build()
|
||||
// .build() returned an ElementState, which is a smart pointer for
|
||||
// calling .build(None) will actually build the DOM elements
|
||||
.build(None)
|
||||
// .build(None) returned an ElementState, which is a smart pointer for
|
||||
// a DOM element. So we can still just call .outer_html(), which access the outerHTML on
|
||||
// the actual DOM element
|
||||
.outer_html()
|
||||
@@ -87,7 +87,7 @@ async fn inc() {
|
||||
|
||||
let _dispose = mount_to(
|
||||
test_wrapper.clone().unchecked_into(),
|
||||
|| view! { <SimpleCounter initial_value=0 step=1/> },
|
||||
|| view! { <SimpleCounter initial_value=0 step=1 /> },
|
||||
);
|
||||
|
||||
// You can do testing with vanilla DOM operations
|
||||
@@ -150,7 +150,7 @@ async fn inc() {
|
||||
}
|
||||
}
|
||||
.into_view()
|
||||
.build()
|
||||
.build(None)
|
||||
.outer_html()
|
||||
);
|
||||
|
||||
@@ -173,7 +173,7 @@ async fn inc() {
|
||||
}
|
||||
}
|
||||
.into_view()
|
||||
.build()
|
||||
.build(None)
|
||||
.outer_html()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_router = { path = "../../router" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
axum = { version = "0.8.1", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
|
||||
@@ -45,7 +45,7 @@ async fn main() {
|
||||
|
||||
// build our application with a route
|
||||
let app = Router::new()
|
||||
.route("/special/:id", get(custom_handler))
|
||||
.route("/special/{id}", get(custom_handler))
|
||||
.leptos_routes(&leptos_options, routes, {
|
||||
let leptos_options = leptos_options.clone();
|
||||
move || shell(leptos_options.clone())
|
||||
|
||||
@@ -20,7 +20,7 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
tracing = "0.1.40"
|
||||
gloo-net = { version = "0.6.0", features = ["http"] }
|
||||
reqwest = { version = "0.12.5", features = ["json"] }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
axum = { version = "0.8.1", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
|
||||
@@ -20,7 +20,7 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
tracing = "0.1.40"
|
||||
gloo-net = { version = "0.6.0", features = ["http"] }
|
||||
reqwest = { version = "0.12.5", features = ["json"] }
|
||||
axum = { version = "0.7.5", optional = true, features = ["http2"] }
|
||||
axum = { version = "0.8.1", optional = true, features = ["http2"] }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = [
|
||||
"fs",
|
||||
|
||||
@@ -23,7 +23,7 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
tracing = "0.1.40"
|
||||
gloo-net = { version = "0.6.0", features = ["http"] }
|
||||
reqwest = { version = "0.12.5", features = ["json"] }
|
||||
axum = { version = "0.7.5", default-features = false, optional = true }
|
||||
axum = { version = "0.8.1", default-features = false, optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
http = { version = "1.1", optional = true }
|
||||
web-sys = { version = "0.3.70", features = [
|
||||
|
||||
@@ -10,15 +10,12 @@ crate-type = ["cdylib", "rlib"]
|
||||
console_error_panic_hook = "0.1.7"
|
||||
futures = "0.3.30"
|
||||
http = "1.1"
|
||||
leptos = { path = "../../leptos", features = [
|
||||
"tracing",
|
||||
"islands",
|
||||
] }
|
||||
leptos = { path = "../../leptos", features = ["tracing", "islands"] }
|
||||
server_fn = { path = "../../server_fn", features = ["serde-lite"] }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
axum = { version = "0.8.1", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
|
||||
@@ -10,10 +10,7 @@ crate-type = ["cdylib", "rlib"]
|
||||
console_error_panic_hook = "0.1.7"
|
||||
futures = "0.3.30"
|
||||
http = "1.1"
|
||||
leptos = { path = "../../leptos", features = [
|
||||
"tracing",
|
||||
"islands",
|
||||
] }
|
||||
leptos = { path = "../../leptos", features = ["tracing", "islands"] }
|
||||
leptos_router = { path = "../../router" }
|
||||
server_fn = { path = "../../server_fn", features = ["serde-lite"] }
|
||||
leptos_axum = { path = "../../integrations/axum", features = [
|
||||
@@ -21,7 +18,7 @@ leptos_axum = { path = "../../integrations/axum", features = [
|
||||
], optional = true }
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
axum = { version = "0.8.1", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
|
||||
@@ -21,7 +21,7 @@ server_fn = { path = "../../server_fn", features = [
|
||||
log = "0.4.22"
|
||||
simple_logger = "5.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
axum = { version = "0.8.1", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = [
|
||||
"fs",
|
||||
|
||||
@@ -9,8 +9,9 @@ use server_fn::{
|
||||
MultipartFormData, Postcard, Rkyv, SerdeLite, StreamingText,
|
||||
TextStream,
|
||||
},
|
||||
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
|
||||
request::{browser::BrowserRequest, ClientReq, Req},
|
||||
response::{browser::BrowserResponse, ClientRes, Res},
|
||||
response::{browser::BrowserResponse, ClientRes, TryRes},
|
||||
};
|
||||
use std::future::Future;
|
||||
#[cfg(feature = "ssr")]
|
||||
@@ -652,32 +653,72 @@ pub fn FileWatcher() -> impl IntoView {
|
||||
/// implementations if you'd like. However, it's much lighter weight to use something like `strum`
|
||||
/// simply to generate those trait implementations.
|
||||
#[server]
|
||||
pub async fn ascii_uppercase(
|
||||
text: String,
|
||||
) -> Result<String, ServerFnError<InvalidArgument>> {
|
||||
pub async fn ascii_uppercase(text: String) -> Result<String, MyErrors> {
|
||||
other_error()?;
|
||||
Ok(ascii_uppercase_inner(text)?)
|
||||
}
|
||||
|
||||
pub fn other_error() -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn ascii_uppercase_inner(text: String) -> Result<String, InvalidArgument> {
|
||||
if text.len() < 5 {
|
||||
Err(InvalidArgument::TooShort.into())
|
||||
Err(InvalidArgument::TooShort)
|
||||
} else if text.len() > 15 {
|
||||
Err(InvalidArgument::TooLong.into())
|
||||
Err(InvalidArgument::TooLong)
|
||||
} else if text.is_ascii() {
|
||||
Ok(text.to_ascii_uppercase())
|
||||
} else {
|
||||
Err(InvalidArgument::NotAscii.into())
|
||||
Err(InvalidArgument::NotAscii)
|
||||
}
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn ascii_uppercase_classic(
|
||||
text: String,
|
||||
) -> Result<String, ServerFnError<InvalidArgument>> {
|
||||
Ok(ascii_uppercase_inner(text)?)
|
||||
}
|
||||
|
||||
// The EnumString and Display derive macros are provided by strum
|
||||
#[derive(Debug, Clone, EnumString, Display)]
|
||||
#[derive(Debug, Clone, Display, EnumString, Serialize, Deserialize)]
|
||||
pub enum InvalidArgument {
|
||||
TooShort,
|
||||
TooLong,
|
||||
NotAscii,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Display, Serialize, Deserialize)]
|
||||
pub enum MyErrors {
|
||||
InvalidArgument(InvalidArgument),
|
||||
ServerFnError(ServerFnErrorErr),
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl From<InvalidArgument> for MyErrors {
|
||||
fn from(value: InvalidArgument) -> Self {
|
||||
MyErrors::InvalidArgument(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for MyErrors {
|
||||
fn from(value: String) -> Self {
|
||||
MyErrors::Other(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromServerFnError for MyErrors {
|
||||
fn from_server_fn_error(value: ServerFnErrorErr) -> Self {
|
||||
MyErrors::ServerFnError(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn CustomErrorTypes() -> impl IntoView {
|
||||
let input_ref = NodeRef::<Input>::new();
|
||||
let (result, set_result) = signal(None);
|
||||
let (result_classic, set_result_classic) = signal(None);
|
||||
|
||||
view! {
|
||||
<h3>Using custom error types</h3>
|
||||
@@ -692,14 +733,17 @@ pub fn CustomErrorTypes() -> impl IntoView {
|
||||
<button on:click=move |_| {
|
||||
let value = input_ref.get().unwrap().value();
|
||||
spawn_local(async move {
|
||||
let data = ascii_uppercase(value).await;
|
||||
let data = ascii_uppercase(value.clone()).await;
|
||||
let data_classic = ascii_uppercase_classic(value).await;
|
||||
set_result.set(Some(data));
|
||||
set_result_classic.set(Some(data_classic));
|
||||
});
|
||||
}>
|
||||
|
||||
"Submit"
|
||||
</button>
|
||||
<p>{move || format!("{:?}", result.get())}</p>
|
||||
<p>{move || format!("{:?}", result_classic.get())}</p>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -726,14 +770,12 @@ impl<T, Request, Err> IntoReq<Toml, Request, Err> for TomlEncoded<T>
|
||||
where
|
||||
Request: ClientReq<Err>,
|
||||
T: Serialize,
|
||||
Err: FromServerFnError,
|
||||
{
|
||||
fn into_req(
|
||||
self,
|
||||
path: &str,
|
||||
accepts: &str,
|
||||
) -> Result<Request, ServerFnError<Err>> {
|
||||
let data = toml::to_string(&self.0)
|
||||
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
|
||||
fn into_req(self, path: &str, accepts: &str) -> Result<Request, Err> {
|
||||
let data = toml::to_string(&self.0).map_err(|e| {
|
||||
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
|
||||
})?;
|
||||
Request::try_new_post(path, Toml::CONTENT_TYPE, accepts, data)
|
||||
}
|
||||
}
|
||||
@@ -742,23 +784,26 @@ impl<T, Request, Err> FromReq<Toml, Request, Err> for TomlEncoded<T>
|
||||
where
|
||||
Request: Req<Err> + Send,
|
||||
T: DeserializeOwned,
|
||||
Err: FromServerFnError,
|
||||
{
|
||||
async fn from_req(req: Request) -> Result<Self, ServerFnError<Err>> {
|
||||
async fn from_req(req: Request) -> Result<Self, Err> {
|
||||
let string_data = req.try_into_string().await?;
|
||||
toml::from_str::<T>(&string_data)
|
||||
.map(TomlEncoded)
|
||||
.map_err(|e| ServerFnError::Args(e.to_string()))
|
||||
.map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Response, Err> IntoRes<Toml, Response, Err> for TomlEncoded<T>
|
||||
where
|
||||
Response: Res<Err>,
|
||||
Response: TryRes<Err>,
|
||||
T: Serialize + Send,
|
||||
Err: FromServerFnError,
|
||||
{
|
||||
async fn into_res(self) -> Result<Response, ServerFnError<Err>> {
|
||||
let data = toml::to_string(&self.0)
|
||||
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
|
||||
async fn into_res(self) -> Result<Response, Err> {
|
||||
let data = toml::to_string(&self.0).map_err(|e| {
|
||||
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
|
||||
})?;
|
||||
Response::try_from_string(Toml::CONTENT_TYPE, data)
|
||||
}
|
||||
}
|
||||
@@ -767,12 +812,13 @@ impl<T, Response, Err> FromRes<Toml, Response, Err> for TomlEncoded<T>
|
||||
where
|
||||
Response: ClientRes<Err> + Send,
|
||||
T: DeserializeOwned,
|
||||
Err: FromServerFnError,
|
||||
{
|
||||
async fn from_res(res: Response) -> Result<Self, ServerFnError<Err>> {
|
||||
async fn from_res(res: Response) -> Result<Self, Err> {
|
||||
let data = res.try_into_string().await?;
|
||||
toml::from_str(&data)
|
||||
.map(TomlEncoded)
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
toml::from_str(&data).map(TomlEncoded).map_err(|e| {
|
||||
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -835,7 +881,10 @@ pub fn CustomClientExample() -> impl IntoView {
|
||||
pub struct CustomClient;
|
||||
|
||||
// Implement the `Client` trait for it.
|
||||
impl<CustErr> Client<CustErr> for CustomClient {
|
||||
impl<E> Client<E> for CustomClient
|
||||
where
|
||||
E: FromServerFnError,
|
||||
{
|
||||
// BrowserRequest and BrowserResponse are the defaults used by other server functions.
|
||||
// They are wrappers for the underlying Web Fetch API types.
|
||||
type Request = BrowserRequest;
|
||||
@@ -844,8 +893,7 @@ pub fn CustomClientExample() -> impl IntoView {
|
||||
// Our custom `send()` implementation does all the work.
|
||||
fn send(
|
||||
req: Self::Request,
|
||||
) -> impl Future<Output = Result<Self::Response, ServerFnError<CustErr>>>
|
||||
+ Send {
|
||||
) -> impl Future<Output = Result<Self::Response, E>> + Send {
|
||||
// BrowserRequest derefs to the underlying Request type from gloo-net,
|
||||
// so we can get access to the headers here
|
||||
let headers = req.headers();
|
||||
|
||||
@@ -20,7 +20,7 @@ leptos_router = { path = "../../router" }
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
thiserror = "1.0"
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
axum = { version = "0.8.1", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = [
|
||||
|
||||
@@ -18,7 +18,7 @@ leptos_router = { path = "../../router" }
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
thiserror = "1.0"
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
axum = { version = "0.8.1", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = [
|
||||
@@ -45,7 +45,7 @@ ssr = [
|
||||
"dep:leptos_axum",
|
||||
"leptos_router/ssr",
|
||||
"dep:notify",
|
||||
"dep:http"
|
||||
"dep:http",
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
|
||||
@@ -7,7 +7,7 @@ edition = "2021"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
axum = { version = "0.8.1", optional = true }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_meta = { path = "../../meta" }
|
||||
|
||||
@@ -16,7 +16,7 @@ leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
log = "0.4.22"
|
||||
simple_logger = "5.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
axum = { version = "0.8.1", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::todo::*;
|
||||
#[cfg(feature = "ssr")]
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::Path,
|
||||
@@ -8,10 +8,9 @@ use axum::{
|
||||
Router,
|
||||
};
|
||||
use leptos::prelude::*;
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
use todo_app_sqlite_axum::*;
|
||||
|
||||
//Define a handler to test extractor with state
|
||||
#[cfg(feature = "ssr")]
|
||||
async fn custom_handler(
|
||||
Path(id): Path<String>,
|
||||
req: Request<Body>,
|
||||
@@ -20,14 +19,16 @@ async fn custom_handler(
|
||||
move || {
|
||||
provide_context(id.clone());
|
||||
},
|
||||
TodoApp,
|
||||
todo::TodoApp,
|
||||
);
|
||||
handler(req).await.into_response()
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
use crate::todo::ssr::db;
|
||||
use crate::todo::{ssr::db, *};
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
|
||||
simple_logger::init_with_level(log::Level::Error)
|
||||
.expect("couldn't initialize logging");
|
||||
@@ -45,7 +46,7 @@ async fn main() {
|
||||
|
||||
// build our application with a route
|
||||
let app = Router::new()
|
||||
.route("/special/:id", get(custom_handler))
|
||||
.route("/special/{id}", get(custom_handler))
|
||||
.leptos_routes(&leptos_options, routes, {
|
||||
let leptos_options = leptos_options.clone();
|
||||
move || shell(leptos_options.clone())
|
||||
@@ -61,3 +62,12 @@ async fn main() {
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
pub fn main() {
|
||||
use leptos::mount::mount_to_body;
|
||||
|
||||
_ = console_log::init_with_level(log::Level::Debug);
|
||||
console_error_panic_hook::set_once();
|
||||
mount_to_body(todo::TodoApp);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ leptos_meta = { path = "../../meta" }
|
||||
leptos_router = { path = "../../router" }
|
||||
leptos_integration_utils = { path = "../../integrations/utils", optional = true }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
axum = { version = "0.8.1", optional = true }
|
||||
tower = { version = "0.5.1", features = ["util"], optional = true }
|
||||
tower-http = { version = "0.6.1", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
|
||||
@@ -34,7 +34,7 @@ async fn main() {
|
||||
// here, we're not actually doing server side rendering, so we set up a manual
|
||||
// handler for the server fns
|
||||
// this should include a get() handler if you have any GetUrl-based server fns
|
||||
.route("/api/*fn_name", post(leptos_axum::handle_server_fns))
|
||||
.route("/api/{*fn_name}", post(leptos_axum::handle_server_fns))
|
||||
.fallback(file_or_index_handler)
|
||||
.with_state(leptos_options);
|
||||
|
||||
|
||||
@@ -369,7 +369,6 @@ pub fn handle_server_fns_with_context(
|
||||
// actually run the server fn
|
||||
let mut res = ActixResponse(
|
||||
service
|
||||
.0
|
||||
.run(ActixRequest::from((req, payload)))
|
||||
.await
|
||||
.take(),
|
||||
|
||||
@@ -11,7 +11,7 @@ edition.workspace = true
|
||||
[dependencies]
|
||||
any_spawner = { workspace = true, features = ["tokio"] }
|
||||
hydration_context = { workspace = true }
|
||||
axum = { version = "0.7.9", default-features = false, features = [
|
||||
axum = { version = "0.8.1", default-features = false, features = [
|
||||
"matched-path",
|
||||
] }
|
||||
dashmap = "6"
|
||||
@@ -30,7 +30,7 @@ tower-http = "0.6.2"
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
axum = "0.7.9"
|
||||
axum = "0.8.1"
|
||||
tokio = { version = "1.41", features = ["net", "rt-multi-thread"] }
|
||||
|
||||
[features]
|
||||
|
||||
@@ -368,8 +368,6 @@ async fn handle_server_fns_inner(
|
||||
additional_context: impl Fn() + 'static + Clone + Send,
|
||||
req: Request<Body>,
|
||||
) -> impl IntoResponse {
|
||||
use server_fn::middleware::Service;
|
||||
|
||||
let method = req.method().clone();
|
||||
let path = req.uri().path().to_string();
|
||||
let (req, parts) = generate_request_and_parts(req);
|
||||
@@ -484,7 +482,7 @@ pub type PinnedHtmlStream =
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
)]
|
||||
pub fn render_app_to_stream<IV>(
|
||||
app_fn: impl Fn() -> IV + Clone + Send + 'static,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + Sync + 'static,
|
||||
) -> impl Fn(
|
||||
Request<Body>,
|
||||
) -> Pin<Box<dyn Future<Output = Response<Body>> + Send + 'static>>
|
||||
@@ -508,7 +506,7 @@ where
|
||||
)]
|
||||
pub fn render_route<S, IV>(
|
||||
paths: Vec<AxumRouteListing>,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + 'static,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + Sync + 'static,
|
||||
) -> impl Fn(
|
||||
State<S>,
|
||||
Request<Body>,
|
||||
@@ -572,7 +570,7 @@ where
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
)]
|
||||
pub fn render_app_to_stream_in_order<IV>(
|
||||
app_fn: impl Fn() -> IV + Clone + Send + 'static,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + Sync + 'static,
|
||||
) -> impl Fn(
|
||||
Request<Body>,
|
||||
) -> Pin<Box<dyn Future<Output = Response<Body>> + Send + 'static>>
|
||||
@@ -625,13 +623,14 @@ where
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
)]
|
||||
pub fn render_app_to_stream_with_context<IV>(
|
||||
additional_context: impl Fn() + 'static + Clone + Send,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + 'static,
|
||||
additional_context: impl Fn() + 'static + Clone + Send + Sync,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + Sync + 'static,
|
||||
) -> impl Fn(
|
||||
Request<Body>,
|
||||
) -> Pin<Box<dyn Future<Output = Response<Body>> + Send + 'static>>
|
||||
+ Clone
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static
|
||||
where
|
||||
IV: IntoView + 'static,
|
||||
@@ -654,8 +653,8 @@ where
|
||||
)]
|
||||
pub fn render_route_with_context<S, IV>(
|
||||
paths: Vec<AxumRouteListing>,
|
||||
additional_context: impl Fn() + 'static + Clone + Send,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + 'static,
|
||||
additional_context: impl Fn() + 'static + Clone + Send + Sync,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + Sync + 'static,
|
||||
) -> impl Fn(
|
||||
State<S>,
|
||||
Request<Body>,
|
||||
@@ -756,14 +755,15 @@ where
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
)]
|
||||
pub fn render_app_to_stream_with_context_and_replace_blocks<IV>(
|
||||
additional_context: impl Fn() + 'static + Clone + Send,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + 'static,
|
||||
additional_context: impl Fn() + 'static + Clone + Send + Sync,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + Sync + 'static,
|
||||
replace_blocks: bool,
|
||||
) -> impl Fn(
|
||||
Request<Body>,
|
||||
) -> Pin<Box<dyn Future<Output = Response<Body>> + Send + 'static>>
|
||||
+ Clone
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static
|
||||
where
|
||||
IV: IntoView + 'static,
|
||||
@@ -823,8 +823,8 @@ where
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
)]
|
||||
pub fn render_app_to_stream_in_order_with_context<IV>(
|
||||
additional_context: impl Fn() + 'static + Clone + Send,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + 'static,
|
||||
additional_context: impl Fn() + 'static + Clone + Send + Sync,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + Sync + 'static,
|
||||
) -> impl Fn(
|
||||
Request<Body>,
|
||||
) -> Pin<Box<dyn Future<Output = Response<Body>> + Send + 'static>>
|
||||
@@ -847,13 +847,17 @@ where
|
||||
}
|
||||
|
||||
fn handle_response<IV>(
|
||||
additional_context: impl Fn() + 'static + Clone + Send,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + 'static,
|
||||
additional_context: impl Fn() + 'static + Clone + Send + Sync,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + Sync + 'static,
|
||||
stream_builder: fn(
|
||||
IV,
|
||||
BoxedFnOnce<PinnedStream<String>>,
|
||||
) -> PinnedFuture<PinnedStream<String>>,
|
||||
) -> impl Fn(Request<Body>) -> PinnedFuture<Response<Body>> + Clone + Send + 'static
|
||||
) -> impl Fn(Request<Body>) -> PinnedFuture<Response<Body>>
|
||||
+ Clone
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static
|
||||
where
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
@@ -980,7 +984,7 @@ fn provide_contexts(
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
)]
|
||||
pub fn render_app_async<IV>(
|
||||
app_fn: impl Fn() -> IV + Clone + Send + 'static,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + Sync + 'static,
|
||||
) -> impl Fn(
|
||||
Request<Body>,
|
||||
) -> Pin<Box<dyn Future<Output = Response<Body>> + Send + 'static>>
|
||||
@@ -1034,8 +1038,8 @@ where
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
)]
|
||||
pub fn render_app_async_stream_with_context<IV>(
|
||||
additional_context: impl Fn() + 'static + Clone + Send,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + 'static,
|
||||
additional_context: impl Fn() + 'static + Clone + Send + Sync,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + Sync + 'static,
|
||||
) -> impl Fn(
|
||||
Request<Body>,
|
||||
) -> Pin<Box<dyn Future<Output = Response<Body>> + Send + 'static>>
|
||||
@@ -1101,8 +1105,8 @@ where
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
)]
|
||||
pub fn render_app_async_with_context<IV>(
|
||||
additional_context: impl Fn() + 'static + Clone + Send,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + 'static,
|
||||
additional_context: impl Fn() + 'static + Clone + Send + Sync,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + Sync + 'static,
|
||||
) -> impl Fn(
|
||||
Request<Body>,
|
||||
) -> Pin<Box<dyn Future<Output = Response<Body>> + Send + 'static>>
|
||||
@@ -1641,7 +1645,7 @@ where
|
||||
self,
|
||||
options: &S,
|
||||
paths: Vec<AxumRouteListing>,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + 'static,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + Sync + 'static,
|
||||
) -> Self
|
||||
where
|
||||
IV: IntoView + 'static;
|
||||
@@ -1656,8 +1660,8 @@ where
|
||||
self,
|
||||
options: &S,
|
||||
paths: Vec<AxumRouteListing>,
|
||||
additional_context: impl Fn() + 'static + Clone + Send,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + 'static,
|
||||
additional_context: impl Fn() + 'static + Clone + Send + Sync,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + Sync + 'static,
|
||||
) -> Self
|
||||
where
|
||||
IV: IntoView + 'static;
|
||||
@@ -1690,12 +1694,15 @@ impl AxumPath for Vec<PathSegment> {
|
||||
match segment {
|
||||
PathSegment::Static(s) => path.push_str(s),
|
||||
PathSegment::Param(s) => {
|
||||
path.push(':');
|
||||
path.push('{');
|
||||
path.push_str(s);
|
||||
path.push('}');
|
||||
}
|
||||
PathSegment::Splat(s) => {
|
||||
path.push('{');
|
||||
path.push('*');
|
||||
path.push_str(s);
|
||||
path.push('}');
|
||||
}
|
||||
PathSegment::Unit => {}
|
||||
PathSegment::OptionalParam(_) => {
|
||||
@@ -1727,7 +1734,7 @@ where
|
||||
self,
|
||||
state: &S,
|
||||
paths: Vec<AxumRouteListing>,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + 'static,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + Sync + 'static,
|
||||
) -> Self
|
||||
where
|
||||
IV: IntoView + 'static,
|
||||
@@ -1743,8 +1750,8 @@ where
|
||||
self,
|
||||
state: &S,
|
||||
paths: Vec<AxumRouteListing>,
|
||||
additional_context: impl Fn() + 'static + Clone + Send,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + 'static,
|
||||
additional_context: impl Fn() + 'static + Clone + Send + Sync,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + Sync + 'static,
|
||||
) -> Self
|
||||
where
|
||||
IV: IntoView + 'static,
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::attr::{
|
||||
Attribute, NextAttribute,
|
||||
};
|
||||
use leptos::prelude::*;
|
||||
use tachys::view::any_view::ExtraAttrsMut;
|
||||
|
||||
/// Function stored to build/rebuild the wrapped children when attributes are added.
|
||||
type ChildBuilder<T> = dyn Fn(AnyAttribute) -> T + Send + Sync + 'static;
|
||||
@@ -43,7 +44,7 @@ pub fn AttributeInterceptor<Chil, T>(
|
||||
) -> impl IntoView
|
||||
where
|
||||
Chil: Fn(AnyAttribute) -> T + Send + Sync + 'static,
|
||||
T: IntoView,
|
||||
T: IntoView + 'static,
|
||||
{
|
||||
AttributeInterceptorInner::new(children)
|
||||
}
|
||||
@@ -77,16 +78,20 @@ impl<T: IntoView> AttributeInterceptorInner<T, ()> {
|
||||
impl<T: IntoView, A: Attribute> Render for AttributeInterceptorInner<T, A> {
|
||||
type State = <T as Render>::State;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
self.children.build()
|
||||
fn build(self, extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
self.children.build(extra_attrs)
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
self.children.rebuild(state);
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
self.children.rebuild(state, extra_attrs);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntoView, A> AddAnyAttr for AttributeInterceptorInner<T, A>
|
||||
impl<T: IntoView + 'static, A> AddAnyAttr for AttributeInterceptorInner<T, A>
|
||||
where
|
||||
A: Attribute,
|
||||
{
|
||||
@@ -114,19 +119,23 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntoView, A: Attribute> RenderHtml for AttributeInterceptorInner<T, A> {
|
||||
impl<T: IntoView + 'static, A: Attribute> RenderHtml
|
||||
for AttributeInterceptorInner<T, A>
|
||||
{
|
||||
type AsyncOutput = T::AsyncOutput;
|
||||
type Owned = AttributeInterceptorInner<T, A::CloneableOwned>;
|
||||
|
||||
const MIN_LENGTH: usize = T::MIN_LENGTH;
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
self.children.dry_resolve()
|
||||
fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>) {
|
||||
self.children.dry_resolve(extra_attrs)
|
||||
}
|
||||
|
||||
fn resolve(
|
||||
self,
|
||||
extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> impl std::future::Future<Output = Self::AsyncOutput> + Send {
|
||||
self.children.resolve()
|
||||
self.children.resolve(extra_attrs)
|
||||
}
|
||||
|
||||
fn to_html_with_buf(
|
||||
@@ -135,16 +144,32 @@ impl<T: IntoView, A: Attribute> RenderHtml for AttributeInterceptorInner<T, A> {
|
||||
position: &mut leptos::tachys::view::Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
self.children
|
||||
.to_html_with_buf(buf, position, escape, mark_branches)
|
||||
self.children.to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
)
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
cursor: &leptos::tachys::hydration::Cursor,
|
||||
position: &leptos::tachys::view::PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
self.children.hydrate::<FROM_SERVER>(cursor, position)
|
||||
self.children
|
||||
.hydrate::<FROM_SERVER>(cursor, position, extra_attrs)
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
AttributeInterceptorInner {
|
||||
children_builder: self.children_builder,
|
||||
children: self.children,
|
||||
attributes: self.attributes.into_cloneable_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,13 +43,20 @@
|
||||
|
||||
use reactive_graph::{
|
||||
owner::{LocalStorage, StoredValue},
|
||||
traits::WithValue,
|
||||
traits::{Dispose, WithValue},
|
||||
};
|
||||
use std::{fmt, rc::Rc, sync::Arc};
|
||||
|
||||
/// A wrapper trait for calling callbacks.
|
||||
pub trait Callable<In: 'static, Out: 'static = ()> {
|
||||
/// calls the callback with the specified argument.
|
||||
///
|
||||
/// Returns None if the callback has been disposed
|
||||
fn try_run(&self, input: In) -> Option<Out>;
|
||||
/// calls the callback with the specified argument.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to run a callback that has been disposed
|
||||
fn run(&self, input: In) -> Out;
|
||||
}
|
||||
|
||||
@@ -72,6 +79,12 @@ impl<In, Out> Clone for UnsyncCallback<In, Out> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<In, Out> Dispose for UnsyncCallback<In, Out> {
|
||||
fn dispose(self) {
|
||||
self.0.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
impl<In, Out> UnsyncCallback<In, Out> {
|
||||
/// Creates a new callback from the given function.
|
||||
pub fn new<F>(f: F) -> UnsyncCallback<In, Out>
|
||||
@@ -93,6 +106,10 @@ impl<In, Out> UnsyncCallback<In, Out> {
|
||||
}
|
||||
|
||||
impl<In: 'static, Out: 'static> Callable<In, Out> for UnsyncCallback<In, Out> {
|
||||
fn try_run(&self, input: In) -> Option<Out> {
|
||||
self.0.try_with_value(|fun| fun(input))
|
||||
}
|
||||
|
||||
fn run(&self, input: In) -> Out {
|
||||
self.0.with_value(|fun| fun(input))
|
||||
}
|
||||
@@ -168,10 +185,12 @@ impl<In, Out> fmt::Debug for Callback<In, Out> {
|
||||
}
|
||||
|
||||
impl<In, Out> Callable<In, Out> for Callback<In, Out> {
|
||||
fn try_run(&self, input: In) -> Option<Out> {
|
||||
self.0.try_with_value(|fun| fun(input))
|
||||
}
|
||||
|
||||
fn run(&self, input: In) -> Out {
|
||||
self.0
|
||||
.try_with_value(|f| f(input))
|
||||
.expect("called a callback that has been disposed")
|
||||
self.0.with_value(|f| f(input))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,6 +200,12 @@ impl<In, Out> Clone for Callback<In, Out> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<In, Out> Dispose for Callback<In, Out> {
|
||||
fn dispose(self) {
|
||||
self.0.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
impl<In, Out> Copy for Callback<In, Out> {}
|
||||
|
||||
macro_rules! impl_callable_from_fn {
|
||||
@@ -239,7 +264,9 @@ impl<In: 'static, Out: 'static> Callback<In, Out> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Callable;
|
||||
use crate::callback::{Callback, UnsyncCallback};
|
||||
use reactive_graph::traits::Dispose;
|
||||
|
||||
struct NoClone {}
|
||||
|
||||
@@ -270,6 +297,22 @@ mod tests {
|
||||
(|num, s| format!("{num} {s}")).into();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_callback_try_run() {
|
||||
let callback = Callback::new(move |arg| arg);
|
||||
assert_eq!(callback.try_run((0,)), Some((0,)));
|
||||
callback.dispose();
|
||||
assert_eq!(callback.try_run((0,)), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsync_callback_try_run() {
|
||||
let callback = UnsyncCallback::new(move |arg| arg);
|
||||
assert_eq!(callback.try_run((0,)), Some((0,)));
|
||||
callback.dispose();
|
||||
assert_eq!(callback.try_run((0,)), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn callback_matches_same() {
|
||||
let callback1 = Callback::new(|x: i32| x * 2);
|
||||
|
||||
@@ -11,13 +11,13 @@ use reactive_graph::{
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::{fmt::Debug, sync::Arc};
|
||||
use tachys::{
|
||||
html::attribute::Attribute,
|
||||
html::attribute::{any_attribute::AnyAttribute, Attribute},
|
||||
hydration::Cursor,
|
||||
reactive_graph::OwnedView,
|
||||
ssr::StreamBuilder,
|
||||
view::{
|
||||
add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
|
||||
RenderHtml,
|
||||
add_attr::AddAnyAttr, any_view::ExtraAttrsMut, Mountable, Position,
|
||||
PositionState, Render, RenderHtml,
|
||||
},
|
||||
};
|
||||
use throw_error::{Error, ErrorHook, ErrorId};
|
||||
@@ -173,10 +173,10 @@ where
|
||||
{
|
||||
type State = RenderEffect<ErrorBoundaryViewState<Chil::State, Fal::State>>;
|
||||
|
||||
fn build(mut self) -> Self::State {
|
||||
fn build(mut self, extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
let hook = Arc::clone(&self.hook);
|
||||
let _hook = throw_error::set_error_hook(Arc::clone(&hook));
|
||||
let mut children = Some(self.children.build());
|
||||
let mut children = Some(self.children.build(extra_attrs.clone()));
|
||||
RenderEffect::new(
|
||||
move |prev: Option<
|
||||
ErrorBoundaryViewState<Chil::State, Fal::State>,
|
||||
@@ -193,7 +193,8 @@ where
|
||||
// yes errors, and was showing children
|
||||
(false, None) => {
|
||||
state.fallback = Some(
|
||||
(self.fallback)(self.errors.clone()).build(),
|
||||
(self.fallback)(self.errors.clone())
|
||||
.build(extra_attrs.clone()),
|
||||
);
|
||||
state
|
||||
.children
|
||||
@@ -207,8 +208,10 @@ where
|
||||
}
|
||||
state
|
||||
} else {
|
||||
let fallback = (!self.errors_empty.get())
|
||||
.then(|| (self.fallback)(self.errors.clone()).build());
|
||||
let fallback = (!self.errors_empty.get()).then(|| {
|
||||
(self.fallback)(self.errors.clone())
|
||||
.build(extra_attrs.clone())
|
||||
});
|
||||
ErrorBoundaryViewState {
|
||||
children: children.take().unwrap(),
|
||||
fallback,
|
||||
@@ -218,8 +221,12 @@ where
|
||||
)
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
let new = self.build();
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
let new = self.build(extra_attrs);
|
||||
let mut old = std::mem::replace(state, new);
|
||||
old.insert_before_this(state);
|
||||
old.unmount();
|
||||
@@ -268,14 +275,18 @@ where
|
||||
Fal: RenderHtml + Send + 'static,
|
||||
{
|
||||
type AsyncOutput = ErrorBoundaryView<Chil::AsyncOutput, FalFn>;
|
||||
type Owned = Self;
|
||||
|
||||
const MIN_LENGTH: usize = Chil::MIN_LENGTH;
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
self.children.dry_resolve();
|
||||
fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>) {
|
||||
self.children.dry_resolve(extra_attrs);
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
async fn resolve(
|
||||
self,
|
||||
extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
let ErrorBoundaryView {
|
||||
hook,
|
||||
boundary_id,
|
||||
@@ -289,7 +300,7 @@ where
|
||||
hook,
|
||||
boundary_id,
|
||||
errors_empty,
|
||||
children: children.resolve().await,
|
||||
children: children.resolve(extra_attrs).await,
|
||||
fallback,
|
||||
errors,
|
||||
}
|
||||
@@ -301,6 +312,7 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
// first, attempt to serialize the children to HTML, then check for errors
|
||||
let _hook = throw_error::set_error_hook(self.hook);
|
||||
@@ -311,6 +323,7 @@ where
|
||||
&mut new_pos,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs.clone(),
|
||||
);
|
||||
|
||||
// any thrown errors would've been caught here
|
||||
@@ -323,6 +336,7 @@ where
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -333,6 +347,7 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -345,6 +360,7 @@ where
|
||||
&mut new_pos,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs.clone(),
|
||||
);
|
||||
|
||||
// any thrown errors would've been caught here
|
||||
@@ -358,6 +374,7 @@ where
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
buf.push_sync(&fallback);
|
||||
}
|
||||
@@ -367,6 +384,7 @@ where
|
||||
mut self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
let mut children = Some(self.children);
|
||||
let hook = Arc::clone(&self.hook);
|
||||
@@ -388,7 +406,8 @@ where
|
||||
// yes errors, and was showing children
|
||||
(false, None) => {
|
||||
state.fallback = Some(
|
||||
(self.fallback)(self.errors.clone()).build(),
|
||||
(self.fallback)(self.errors.clone())
|
||||
.build(extra_attrs.clone()),
|
||||
);
|
||||
state
|
||||
.children
|
||||
@@ -405,15 +424,23 @@ where
|
||||
let children = children.take().unwrap();
|
||||
let (children, fallback) = if self.errors_empty.get() {
|
||||
(
|
||||
children.hydrate::<FROM_SERVER>(&cursor, &position),
|
||||
children.hydrate::<FROM_SERVER>(
|
||||
&cursor,
|
||||
&position,
|
||||
extra_attrs.clone(),
|
||||
),
|
||||
None,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
children.build(),
|
||||
children.build(extra_attrs.clone()),
|
||||
Some(
|
||||
(self.fallback)(self.errors.clone())
|
||||
.hydrate::<FROM_SERVER>(&cursor, &position),
|
||||
.hydrate::<FROM_SERVER>(
|
||||
&cursor,
|
||||
&position,
|
||||
extra_attrs.clone(),
|
||||
),
|
||||
),
|
||||
)
|
||||
};
|
||||
@@ -423,6 +450,10 @@ where
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -3,7 +3,11 @@ use leptos_dom::helpers::window;
|
||||
use leptos_server::{ServerAction, ServerMultiAction};
|
||||
use serde::de::DeserializeOwned;
|
||||
use server_fn::{
|
||||
client::Client, codec::PostUrl, request::ClientReq, ServerFn, ServerFnError,
|
||||
client::Client,
|
||||
codec::PostUrl,
|
||||
error::{IntoAppError, ServerFnErrorErr},
|
||||
request::ClientReq,
|
||||
ServerFn,
|
||||
};
|
||||
use tachys::{
|
||||
either::Either,
|
||||
@@ -121,9 +125,10 @@ where
|
||||
"Error converting form field into server function \
|
||||
arguments: {err:?}"
|
||||
);
|
||||
value.set(Some(Err(ServerFnError::Serialization(
|
||||
value.set(Some(Err(ServerFnErrorErr::Serialization(
|
||||
err.to_string(),
|
||||
))));
|
||||
)
|
||||
.into_app_error())));
|
||||
version.update(|n| *n += 1);
|
||||
}
|
||||
}
|
||||
@@ -187,9 +192,10 @@ where
|
||||
action.dispatch(new_input);
|
||||
}
|
||||
Err(err) => {
|
||||
action.dispatch_sync(Err(ServerFnError::Serialization(
|
||||
action.dispatch_sync(Err(ServerFnErrorErr::Serialization(
|
||||
err.to_string(),
|
||||
)));
|
||||
)
|
||||
.into_app_error()));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use std::borrow::Cow;
|
||||
use tachys::{
|
||||
html::attribute::Attribute,
|
||||
html::attribute::{any_attribute::AnyAttribute, Attribute},
|
||||
hydration::Cursor,
|
||||
ssr::StreamBuilder,
|
||||
view::{
|
||||
add_attr::AddAnyAttr, Position, PositionState, Render, RenderHtml,
|
||||
ToTemplate,
|
||||
add_attr::AddAnyAttr, any_view::ExtraAttrsMut, Position, PositionState,
|
||||
Render, RenderHtml, ToTemplate,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -76,26 +76,34 @@ where
|
||||
impl<T: Render> Render for View<T> {
|
||||
type State = T::State;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
self.inner.build()
|
||||
fn build(self, extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
self.inner.build(extra_attrs)
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
self.inner.rebuild(state)
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
self.inner.rebuild(state, extra_attrs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RenderHtml> RenderHtml for View<T> {
|
||||
type AsyncOutput = T::AsyncOutput;
|
||||
type Owned = View<T::Owned>;
|
||||
|
||||
const MIN_LENGTH: usize = <T as RenderHtml>::MIN_LENGTH;
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
self.inner.resolve().await
|
||||
async fn resolve(
|
||||
self,
|
||||
extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
self.inner.resolve(extra_attrs).await
|
||||
}
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
self.inner.dry_resolve();
|
||||
fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>) {
|
||||
self.inner.dry_resolve(extra_attrs);
|
||||
}
|
||||
|
||||
fn to_html_with_buf(
|
||||
@@ -104,6 +112,7 @@ impl<T: RenderHtml> RenderHtml for View<T> {
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
#[cfg(debug_assertions)]
|
||||
let vm = self.view_marker.to_owned();
|
||||
@@ -112,8 +121,13 @@ impl<T: RenderHtml> RenderHtml for View<T> {
|
||||
buf.push_str(&format!("<!--hot-reload|{vm}|open-->"));
|
||||
}
|
||||
|
||||
self.inner
|
||||
.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
self.inner.to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
if let Some(vm) = vm.as_ref() {
|
||||
@@ -127,6 +141,7 @@ impl<T: RenderHtml> RenderHtml for View<T> {
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -142,6 +157,7 @@ impl<T: RenderHtml> RenderHtml for View<T> {
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -154,8 +170,18 @@ impl<T: RenderHtml> RenderHtml for View<T> {
|
||||
self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
self.inner.hydrate::<FROM_SERVER>(cursor, position)
|
||||
self.inner
|
||||
.hydrate::<FROM_SERVER>(cursor, position, extra_attrs)
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
View {
|
||||
inner: self.inner.into_owned(),
|
||||
#[cfg(debug_assertions)]
|
||||
view_marker: self.view_marker,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ pub mod prelude {
|
||||
actions::*, computed::*, effect::*, graph::untrack, owner::*,
|
||||
signal::*, wrappers::read::*,
|
||||
};
|
||||
pub use server_fn::{self, ServerFnError};
|
||||
pub use server_fn::{self, error::ServerFnError};
|
||||
pub use tachys::{
|
||||
reactive_graph::{bind::BindAttribute, node_ref::*, Suspend},
|
||||
view::{
|
||||
|
||||
@@ -71,6 +71,7 @@ where
|
||||
view.hydrate::<true>(
|
||||
&Cursor::new(parent.unchecked_into()),
|
||||
&PositionState::default(),
|
||||
None,
|
||||
)
|
||||
});
|
||||
|
||||
@@ -124,7 +125,7 @@ where
|
||||
let owner = Owner::new();
|
||||
let mountable = owner.with(move || {
|
||||
let view = f().into_view();
|
||||
let mut mountable = view.build();
|
||||
let mut mountable = view.build(None);
|
||||
mountable.mount(&parent, None);
|
||||
mountable
|
||||
});
|
||||
@@ -152,7 +153,7 @@ where
|
||||
let owner = Owner::new();
|
||||
let mountable = owner.with(move || {
|
||||
let view = f();
|
||||
let mut mountable = view.build();
|
||||
let mut mountable = view.build(None);
|
||||
mountable.mount(parent, None);
|
||||
mountable
|
||||
});
|
||||
|
||||
@@ -19,12 +19,13 @@ use slotmap::{DefaultKey, SlotMap};
|
||||
use std::sync::Arc;
|
||||
use tachys::{
|
||||
either::Either,
|
||||
html::attribute::Attribute,
|
||||
html::attribute::{any_attribute::AnyAttribute, Attribute},
|
||||
hydration::Cursor,
|
||||
reactive_graph::{OwnedView, OwnedViewState},
|
||||
ssr::StreamBuilder,
|
||||
view::{
|
||||
add_attr::AddAnyAttr,
|
||||
any_view::ExtraAttrsMut,
|
||||
either::{EitherKeepAlive, EitherKeepAliveState},
|
||||
Mountable, Position, PositionState, Render, RenderHtml,
|
||||
},
|
||||
@@ -162,7 +163,7 @@ where
|
||||
OwnedViewState<EitherKeepAliveState<Chil::State, Fal::State>>,
|
||||
>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
fn build(self, extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
let mut children = Some(self.children);
|
||||
let mut fallback = Some(self.fallback);
|
||||
let none_pending = self.none_pending;
|
||||
@@ -187,16 +188,20 @@ where
|
||||
);
|
||||
|
||||
if let Some(mut state) = prev {
|
||||
this.rebuild(&mut state);
|
||||
this.rebuild(&mut state, extra_attrs.clone());
|
||||
state
|
||||
} else {
|
||||
this.build()
|
||||
this.build(extra_attrs.clone())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
let new = self.build();
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
let new = self.build(extra_attrs);
|
||||
let mut old = std::mem::replace(state, new);
|
||||
old.insert_before_this(state);
|
||||
old.unmount();
|
||||
@@ -247,12 +252,16 @@ where
|
||||
// i.e., if this is the child of another Suspense during SSR, don't wait for it: it will handle
|
||||
// itself
|
||||
type AsyncOutput = Self;
|
||||
type Owned = Self;
|
||||
|
||||
const MIN_LENGTH: usize = Chil::MIN_LENGTH;
|
||||
|
||||
fn dry_resolve(&mut self) {}
|
||||
fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
async fn resolve(
|
||||
self,
|
||||
_extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
self
|
||||
}
|
||||
|
||||
@@ -262,9 +271,15 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
self.fallback
|
||||
.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
self.fallback.to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
}
|
||||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
@@ -273,6 +288,7 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
mut extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -297,7 +313,8 @@ where
|
||||
provide_context(LocalResourceNotifier::from(local_tx));
|
||||
|
||||
// walk over the tree of children once to make sure that all resource loads are registered
|
||||
self.children.dry_resolve();
|
||||
self.children
|
||||
.dry_resolve(ExtraAttrsMut::from_owned(&mut extra_attrs));
|
||||
|
||||
// check the set of tasks to see if it is empty, now or later
|
||||
let eff = reactive_graph::effect::Effect::new_isomorphic({
|
||||
@@ -313,7 +330,8 @@ where
|
||||
}
|
||||
});
|
||||
|
||||
let mut fut = Box::pin(ScopedFuture::new(ErrorHookFuture::new(
|
||||
let mut fut = Box::pin(ScopedFuture::new(ErrorHookFuture::new({
|
||||
let mut extra_attrs = extra_attrs.clone();
|
||||
async move {
|
||||
// race the local resource notifier against the set of tasks
|
||||
//
|
||||
@@ -340,7 +358,7 @@ where
|
||||
// but in situations like a <For each=|| some_resource.snapshot()/> we actually
|
||||
// want to be able to 1) synchronously read a resource's value, but still 2) wait
|
||||
// for it to load before we render everything
|
||||
let mut children = Box::pin(self.children.resolve().fuse());
|
||||
let mut children = Box::pin(self.children.resolve(ExtraAttrsMut::from_owned(&mut extra_attrs)).fuse());
|
||||
|
||||
// we continue racing the children against the "do we have any local
|
||||
// resources?" Future
|
||||
@@ -359,8 +377,8 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)));
|
||||
}
|
||||
})));
|
||||
match fut.as_mut().now_or_never() {
|
||||
Some(Some(resolved)) => {
|
||||
Either::<Fal, _>::Right(resolved)
|
||||
@@ -369,6 +387,7 @@ where
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
}
|
||||
Some(None) => {
|
||||
@@ -378,6 +397,7 @@ where
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
}
|
||||
None => {
|
||||
@@ -391,12 +411,14 @@ where
|
||||
self.fallback,
|
||||
&mut fallback_position,
|
||||
mark_branches,
|
||||
extra_attrs.clone(),
|
||||
);
|
||||
buf.push_async_out_of_order_with_nonce(
|
||||
fut,
|
||||
position,
|
||||
mark_branches,
|
||||
nonce_or_not(),
|
||||
extra_attrs,
|
||||
);
|
||||
} else {
|
||||
buf.push_async({
|
||||
@@ -412,6 +434,7 @@ where
|
||||
&mut position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
builder.finish().take_chunks()
|
||||
}
|
||||
@@ -426,6 +449,7 @@ where
|
||||
self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
let cursor = cursor.to_owned();
|
||||
let position = position.to_owned();
|
||||
@@ -454,13 +478,21 @@ where
|
||||
);
|
||||
|
||||
if let Some(mut state) = prev {
|
||||
this.rebuild(&mut state);
|
||||
this.rebuild(&mut state, extra_attrs.clone());
|
||||
state
|
||||
} else {
|
||||
this.hydrate::<FROM_SERVER>(&cursor, &position)
|
||||
this.hydrate::<FROM_SERVER>(
|
||||
&cursor,
|
||||
&position,
|
||||
extra_attrs.clone(),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper that prevents [`Suspense`] from waiting for any resource reads that happen inside
|
||||
@@ -480,12 +512,16 @@ where
|
||||
{
|
||||
type State = T::State;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
(self.0)().build()
|
||||
fn build(self, extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
(self.0)().build(extra_attrs)
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
(self.0)().rebuild(state);
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
(self.0)().rebuild(state, extra_attrs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,12 +549,16 @@ where
|
||||
T: RenderHtml + 'static,
|
||||
{
|
||||
type AsyncOutput = Self;
|
||||
type Owned = Self;
|
||||
|
||||
const MIN_LENGTH: usize = T::MIN_LENGTH;
|
||||
|
||||
fn dry_resolve(&mut self) {}
|
||||
fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
async fn resolve(
|
||||
self,
|
||||
_extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
self
|
||||
}
|
||||
|
||||
@@ -528,8 +568,15 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
(self.0)().to_html_with_buf(buf, position, escape, mark_branches);
|
||||
(self.0)().to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
}
|
||||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
@@ -538,6 +585,7 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -546,6 +594,7 @@ where
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -553,7 +602,12 @@ where
|
||||
self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
(self.0)().hydrate::<FROM_SERVER>(cursor, position)
|
||||
(self.0)().hydrate::<FROM_SERVER>(cursor, position, extra_attrs)
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,9 @@ use typed_builder::TypedBuilder;
|
||||
|
||||
/// A Struct to allow us to parse LeptosOptions from the file. Not really needed, most interactions should
|
||||
/// occur with LeptosOptions
|
||||
#[derive(Clone, Debug, serde::Deserialize, Default)]
|
||||
#[derive(Clone, Debug, serde::Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[non_exhaustive]
|
||||
pub struct ConfFile {
|
||||
pub leptos_options: LeptosOptions,
|
||||
}
|
||||
@@ -24,9 +25,14 @@ pub struct ConfFile {
|
||||
/// It shares keys with cargo-leptos, to allow for easy interoperability
|
||||
#[derive(TypedBuilder, Debug, Clone, serde::Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[non_exhaustive]
|
||||
pub struct LeptosOptions {
|
||||
/// The name of the WASM and JS files generated by wasm-bindgen. Defaults to the crate name with underscores instead of dashes
|
||||
#[builder(setter(into), default=default_output_name())]
|
||||
/// The name of the WASM and JS files generated by wasm-bindgen.
|
||||
///
|
||||
/// This should match the name that will be output when building your application.
|
||||
///
|
||||
/// You can easily set this using `env!("CARGO_CRATE_NAME")`.
|
||||
#[builder(setter(into))]
|
||||
pub output_name: Arc<str>,
|
||||
/// The path of the all the files generated by cargo-leptos. This defaults to '.' for convenience when integrating with other
|
||||
/// tools.
|
||||
@@ -78,6 +84,40 @@ pub struct LeptosOptions {
|
||||
#[builder(default = default_hash_files())]
|
||||
#[serde(default = "default_hash_files")]
|
||||
pub hash_files: bool,
|
||||
/// The default prefix to use for server functions when generating API routes. Can be
|
||||
/// overridden for individual functions using `#[server(prefix = "...")]` as usual.
|
||||
///
|
||||
/// This is useful to override the default prefix (`/api`) for all server functions without
|
||||
/// needing to manually specify via `#[server(prefix = "...")]` on every server function.
|
||||
#[builder(default, setter(strip_option))]
|
||||
#[serde(default)]
|
||||
pub server_fn_prefix: Option<String>,
|
||||
/// Whether to disable appending the server functions' hashes to the end of their API names.
|
||||
///
|
||||
/// This is useful when an app's client side needs a stable server API. For example, shipping
|
||||
/// the CSR WASM binary in a Tauri app. Tauri app releases are dependent on each platform's
|
||||
/// distribution method (e.g., the Apple App Store or the Google Play Store), which typically
|
||||
/// are much slower than the frequency at which a website can be updated. In addition, it's
|
||||
/// common for users to not have the latest app version installed. In these cases, the CSR WASM
|
||||
/// app would need to be able to continue calling the backend server function API, so the API
|
||||
/// path needs to be consistent and not have a hash appended.
|
||||
///
|
||||
/// Note that the hash suffixes is intended as a way to ensure duplicate API routes are created.
|
||||
/// Without the hash, server functions will need to have unique names to avoid creating
|
||||
/// duplicate routes. Axum will throw an error if a duplicate route is added to the router, but
|
||||
/// Actix will not.
|
||||
#[builder(default)]
|
||||
#[serde(default)]
|
||||
pub disable_server_fn_hash: bool,
|
||||
/// Include the module path of the server function in the API route. This is an alternative
|
||||
/// strategy to prevent duplicate server function API routes (the default strategy is to add
|
||||
/// a hash to the end of the route). Each element of the module path will be separated by a `/`.
|
||||
/// For example, a server function with a fully qualified name of `parent::child::server_fn`
|
||||
/// would have an API route of `/api/parent/child/server_fn` (possibly with a
|
||||
/// different prefix and a hash suffix depending on the values of the other server fn configs).
|
||||
#[builder(default)]
|
||||
#[serde(default)]
|
||||
pub server_fn_mod_path: bool,
|
||||
}
|
||||
|
||||
impl LeptosOptions {
|
||||
@@ -120,20 +160,14 @@ impl LeptosOptions {
|
||||
hash_file: env_w_default("LEPTOS_HASH_FILE_NAME", "hash.txt")?
|
||||
.into(),
|
||||
hash_files: env_w_default("LEPTOS_HASH_FILES", "false")?.parse()?,
|
||||
server_fn_prefix: env_wo_default("SERVER_FN_PREFIX")?,
|
||||
disable_server_fn_hash: env_wo_default("DISABLE_SERVER_FN_HASH")?
|
||||
.is_some(),
|
||||
server_fn_mod_path: env_wo_default("SERVER_FN_MOD_PATH")?.is_some(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LeptosOptions {
|
||||
fn default() -> Self {
|
||||
LeptosOptions::builder().build()
|
||||
}
|
||||
}
|
||||
|
||||
fn default_output_name() -> Arc<str> {
|
||||
env!("CARGO_CRATE_NAME").replace('-', "_").into()
|
||||
}
|
||||
|
||||
fn default_site_root() -> Arc<str> {
|
||||
".".into()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_macro"
|
||||
version = "0.7.4"
|
||||
version = "0.8.0-alpha"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
|
||||
@@ -919,7 +919,7 @@ pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
args.into(),
|
||||
s.into(),
|
||||
Some(syn::parse_quote!(::leptos::server_fn)),
|
||||
"/api",
|
||||
option_env!("SERVER_FN_PREFIX").unwrap_or("/api"),
|
||||
None,
|
||||
None,
|
||||
) {
|
||||
|
||||
@@ -3,7 +3,7 @@ use reactive_graph::{
|
||||
owner::use_context,
|
||||
traits::DefinedAt,
|
||||
};
|
||||
use server_fn::{error::ServerFnErrorSerde, ServerFn, ServerFnError};
|
||||
use server_fn::{error::FromServerFnError, ServerFn};
|
||||
use std::{ops::Deref, panic::Location, sync::Arc};
|
||||
|
||||
/// An error that can be caused by a server action.
|
||||
@@ -42,7 +42,7 @@ where
|
||||
S: ServerFn + 'static,
|
||||
S::Output: 'static,
|
||||
{
|
||||
inner: ArcAction<S, Result<S::Output, ServerFnError<S::Error>>>,
|
||||
inner: ArcAction<S, Result<S::Output, S::Error>>,
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
@@ -52,13 +52,14 @@ where
|
||||
S: ServerFn + Clone + Send + Sync + 'static,
|
||||
S::Output: Send + Sync + 'static,
|
||||
S::Error: Send + Sync + 'static,
|
||||
S::Error: FromServerFnError,
|
||||
{
|
||||
/// Creates a new [`ArcAction`] that will call the server function `S` when dispatched.
|
||||
#[track_caller]
|
||||
pub fn new() -> Self {
|
||||
let err = use_context::<ServerActionError>().and_then(|error| {
|
||||
(error.path() == S::PATH)
|
||||
.then(|| ServerFnError::<S::Error>::de(error.err()))
|
||||
.then(|| S::Error::de(error.err()))
|
||||
.map(Err)
|
||||
});
|
||||
Self {
|
||||
@@ -76,7 +77,7 @@ where
|
||||
S: ServerFn + 'static,
|
||||
S::Output: 'static,
|
||||
{
|
||||
type Target = ArcAction<S, Result<S::Output, ServerFnError<S::Error>>>;
|
||||
type Target = ArcAction<S, Result<S::Output, S::Error>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
@@ -131,7 +132,7 @@ where
|
||||
S: ServerFn + 'static,
|
||||
S::Output: 'static,
|
||||
{
|
||||
inner: Action<S, Result<S::Output, ServerFnError<S::Error>>>,
|
||||
inner: Action<S, Result<S::Output, S::Error>>,
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
@@ -146,7 +147,7 @@ where
|
||||
pub fn new() -> Self {
|
||||
let err = use_context::<ServerActionError>().and_then(|error| {
|
||||
(error.path() == S::PATH)
|
||||
.then(|| ServerFnError::<S::Error>::de(error.err()))
|
||||
.then(|| S::Error::de(error.err()))
|
||||
.map(Err)
|
||||
});
|
||||
Self {
|
||||
@@ -182,15 +183,14 @@ where
|
||||
S::Output: Send + Sync + 'static,
|
||||
S::Error: Send + Sync + 'static,
|
||||
{
|
||||
type Target = Action<S, Result<S::Output, ServerFnError<S::Error>>>;
|
||||
type Target = Action<S, Result<S::Output, S::Error>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> From<ServerAction<S>>
|
||||
for Action<S, Result<S::Output, ServerFnError<S::Error>>>
|
||||
impl<S> From<ServerAction<S>> for Action<S, Result<S::Output, S::Error>>
|
||||
where
|
||||
S: ServerFn + 'static,
|
||||
S::Output: 'static,
|
||||
|
||||
@@ -79,12 +79,13 @@ mod view_implementations {
|
||||
use reactive_graph::traits::Read;
|
||||
use std::future::Future;
|
||||
use tachys::{
|
||||
html::attribute::Attribute,
|
||||
html::attribute::{any_attribute::AnyAttribute, Attribute},
|
||||
hydration::Cursor,
|
||||
reactive_graph::{RenderEffectState, Suspend, SuspendState},
|
||||
ssr::StreamBuilder,
|
||||
view::{
|
||||
add_attr::AddAnyAttr, Position, PositionState, Render, RenderHtml,
|
||||
add_attr::AddAnyAttr, any_view::ExtraAttrsMut, Position,
|
||||
PositionState, Render, RenderHtml,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -95,12 +96,17 @@ mod view_implementations {
|
||||
{
|
||||
type State = RenderEffectState<SuspendState<T>>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
(move || Suspend::new(async move { self.await })).build()
|
||||
fn build(self, extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
(move || Suspend::new(async move { self.await })).build(extra_attrs)
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
(move || Suspend::new(async move { self.await })).rebuild(state)
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
(move || Suspend::new(async move { self.await }))
|
||||
.rebuild(state, extra_attrs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,15 +141,20 @@ mod view_implementations {
|
||||
Ser: Send + 'static,
|
||||
{
|
||||
type AsyncOutput = Option<T>;
|
||||
type Owned = Self;
|
||||
|
||||
const MIN_LENGTH: usize = 0;
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {
|
||||
self.read();
|
||||
}
|
||||
|
||||
fn resolve(self) -> impl Future<Output = Self::AsyncOutput> + Send {
|
||||
(move || Suspend::new(async move { self.await })).resolve()
|
||||
fn resolve(
|
||||
self,
|
||||
extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> impl Future<Output = Self::AsyncOutput> + Send {
|
||||
(move || Suspend::new(async move { self.await }))
|
||||
.resolve(extra_attrs)
|
||||
}
|
||||
|
||||
fn to_html_with_buf(
|
||||
@@ -152,12 +163,14 @@ mod view_implementations {
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
(move || Suspend::new(async move { self.await })).to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -167,6 +180,7 @@ mod view_implementations {
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -176,6 +190,7 @@ mod view_implementations {
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -183,9 +198,14 @@ mod view_implementations {
|
||||
self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
(move || Suspend::new(async move { self.await }))
|
||||
.hydrate::<FROM_SERVER>(cursor, position)
|
||||
.hydrate::<FROM_SERVER>(cursor, position, extra_attrs)
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use reactive_graph::{
|
||||
actions::{ArcMultiAction, MultiAction},
|
||||
traits::DefinedAt,
|
||||
};
|
||||
use server_fn::{ServerFn, ServerFnError};
|
||||
use server_fn::ServerFn;
|
||||
use std::{ops::Deref, panic::Location};
|
||||
|
||||
/// An [`ArcMultiAction`] that can be used to call a server function.
|
||||
@@ -11,7 +11,7 @@ where
|
||||
S: ServerFn + 'static,
|
||||
S::Output: 'static,
|
||||
{
|
||||
inner: ArcMultiAction<S, Result<S::Output, ServerFnError<S::Error>>>,
|
||||
inner: ArcMultiAction<S, Result<S::Output, S::Error>>,
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
@@ -40,7 +40,7 @@ where
|
||||
S: ServerFn + 'static,
|
||||
S::Output: 'static,
|
||||
{
|
||||
type Target = ArcMultiAction<S, Result<S::Output, ServerFnError<S::Error>>>;
|
||||
type Target = ArcMultiAction<S, Result<S::Output, S::Error>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
@@ -95,13 +95,13 @@ where
|
||||
S: ServerFn + 'static,
|
||||
S::Output: 'static,
|
||||
{
|
||||
inner: MultiAction<S, Result<S::Output, ServerFnError<S::Error>>>,
|
||||
inner: MultiAction<S, Result<S::Output, S::Error>>,
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
impl<S> From<ServerMultiAction<S>>
|
||||
for MultiAction<S, Result<S::Output, ServerFnError<S::Error>>>
|
||||
for MultiAction<S, Result<S::Output, S::Error>>
|
||||
where
|
||||
S: ServerFn + 'static,
|
||||
S::Output: 'static,
|
||||
@@ -152,7 +152,7 @@ where
|
||||
S::Output: 'static,
|
||||
S::Error: 'static,
|
||||
{
|
||||
type Target = MultiAction<S, Result<S::Output, ServerFnError<S::Error>>>;
|
||||
type Target = MultiAction<S, Result<S::Output, S::Error>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_meta"
|
||||
version = "0.7.4"
|
||||
version = "0.8.0-alpha"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use crate::ServerMetaContext;
|
||||
use leptos::{
|
||||
attr::NextAttribute,
|
||||
attr::{
|
||||
any_attribute::{AnyAttribute, AnyAttributeState},
|
||||
NextAttribute,
|
||||
},
|
||||
component, html,
|
||||
reactive::owner::use_context,
|
||||
tachys::{
|
||||
@@ -8,8 +11,8 @@ use leptos::{
|
||||
html::attribute::Attribute,
|
||||
hydration::Cursor,
|
||||
view::{
|
||||
add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
|
||||
RenderHtml,
|
||||
add_attr::AddAnyAttr, any_view::ExtraAttrsMut, Mountable, Position,
|
||||
PositionState, Render, RenderHtml,
|
||||
},
|
||||
},
|
||||
IntoView,
|
||||
@@ -58,6 +61,7 @@ where
|
||||
At: Attribute,
|
||||
{
|
||||
attributes: At::State,
|
||||
extra_attrs: Option<Vec<AnyAttributeState>>,
|
||||
}
|
||||
|
||||
impl<At> Render for BodyView<At>
|
||||
@@ -66,15 +70,27 @@ where
|
||||
{
|
||||
type State = BodyViewState<At>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
fn build(self, extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
let el = document().body().expect("there to be a <body> element");
|
||||
let attributes = self.attributes.build(&el);
|
||||
|
||||
BodyViewState { attributes }
|
||||
let extra_attrs = extra_attrs.map(|attrs| attrs.build(&el));
|
||||
BodyViewState {
|
||||
attributes,
|
||||
extra_attrs,
|
||||
}
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
self.attributes.rebuild(&mut state.attributes);
|
||||
if let (Some(extra_attrs), Some(extra_attr_states)) =
|
||||
(extra_attrs, &mut state.extra_attrs)
|
||||
{
|
||||
extra_attrs.rebuild(extra_attr_states);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,17 +119,24 @@ where
|
||||
At: Attribute,
|
||||
{
|
||||
type AsyncOutput = BodyView<At::AsyncOutput>;
|
||||
type Owned = BodyView<At::CloneableOwned>;
|
||||
|
||||
const MIN_LENGTH: usize = At::MIN_LENGTH;
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
fn dry_resolve(&mut self, mut extra_attrs: ExtraAttrsMut<'_>) {
|
||||
self.attributes.dry_resolve();
|
||||
extra_attrs.iter_mut().for_each(Attribute::dry_resolve);
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
BodyView {
|
||||
attributes: self.attributes.resolve().await,
|
||||
}
|
||||
async fn resolve(
|
||||
self,
|
||||
extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
let (attributes, _) = futures::join!(
|
||||
self.attributes.resolve(),
|
||||
ExtraAttrsMut::resolve(extra_attrs)
|
||||
);
|
||||
BodyView { attributes }
|
||||
}
|
||||
|
||||
fn to_html_with_buf(
|
||||
@@ -122,10 +145,15 @@ where
|
||||
_position: &mut Position,
|
||||
_escape: bool,
|
||||
_mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
if let Some(meta) = use_context::<ServerMetaContext>() {
|
||||
let mut buf = String::new();
|
||||
_ = html::attributes_to_html(self.attributes, &mut buf);
|
||||
_ = html::attributes_to_html(
|
||||
self.attributes,
|
||||
extra_attrs,
|
||||
&mut buf,
|
||||
);
|
||||
if !buf.is_empty() {
|
||||
_ = meta.body.send(buf);
|
||||
}
|
||||
@@ -136,11 +164,23 @@ where
|
||||
self,
|
||||
_cursor: &Cursor,
|
||||
_position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
let el = document().body().expect("there to be a <body> element");
|
||||
let attributes = self.attributes.hydrate::<FROM_SERVER>(&el);
|
||||
let extra_attrs =
|
||||
extra_attrs.map(|attrs| attrs.hydrate::<FROM_SERVER>(&el));
|
||||
|
||||
BodyViewState { attributes }
|
||||
BodyViewState {
|
||||
attributes,
|
||||
extra_attrs,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
BodyView {
|
||||
attributes: self.attributes.into_cloneable_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use crate::ServerMetaContext;
|
||||
use leptos::{
|
||||
attr::NextAttribute,
|
||||
attr::{
|
||||
any_attribute::{AnyAttribute, AnyAttributeState},
|
||||
NextAttribute,
|
||||
},
|
||||
component, html,
|
||||
reactive::owner::use_context,
|
||||
tachys::{
|
||||
@@ -8,8 +11,8 @@ use leptos::{
|
||||
html::attribute::Attribute,
|
||||
hydration::Cursor,
|
||||
view::{
|
||||
add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
|
||||
RenderHtml,
|
||||
add_attr::AddAnyAttr, any_view::ExtraAttrsMut, Mountable, Position,
|
||||
PositionState, Render, RenderHtml,
|
||||
},
|
||||
},
|
||||
IntoView,
|
||||
@@ -55,6 +58,7 @@ where
|
||||
At: Attribute,
|
||||
{
|
||||
attributes: At::State,
|
||||
extra_attrs: Option<Vec<AnyAttributeState>>,
|
||||
}
|
||||
|
||||
impl<At> Render for HtmlView<At>
|
||||
@@ -63,18 +67,33 @@ where
|
||||
{
|
||||
type State = HtmlViewState<At>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
fn build(self, extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
let el = document()
|
||||
.document_element()
|
||||
.expect("there to be a <html> element");
|
||||
|
||||
let attributes = self.attributes.build(&el);
|
||||
let extra_attrs = extra_attrs.map(|attrs| {
|
||||
attrs.into_iter().map(|attr| attr.build(&el)).collect()
|
||||
});
|
||||
|
||||
HtmlViewState { attributes }
|
||||
HtmlViewState {
|
||||
attributes,
|
||||
extra_attrs,
|
||||
}
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
self.attributes.rebuild(&mut state.attributes);
|
||||
if let (Some(extra_attrs), Some(extra_attr_states)) =
|
||||
(extra_attrs, &mut state.extra_attrs)
|
||||
{
|
||||
extra_attrs.rebuild(extra_attr_states);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,17 +122,24 @@ where
|
||||
At: Attribute,
|
||||
{
|
||||
type AsyncOutput = HtmlView<At::AsyncOutput>;
|
||||
type Owned = HtmlView<At::CloneableOwned>;
|
||||
|
||||
const MIN_LENGTH: usize = At::MIN_LENGTH;
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
fn dry_resolve(&mut self, mut extra_attrs: ExtraAttrsMut<'_>) {
|
||||
self.attributes.dry_resolve();
|
||||
extra_attrs.iter_mut().for_each(Attribute::dry_resolve);
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
HtmlView {
|
||||
attributes: self.attributes.resolve().await,
|
||||
}
|
||||
async fn resolve(
|
||||
self,
|
||||
extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
let (attributes, _) = futures::join!(
|
||||
self.attributes.resolve(),
|
||||
ExtraAttrsMut::resolve(extra_attrs)
|
||||
);
|
||||
HtmlView { attributes }
|
||||
}
|
||||
|
||||
fn to_html_with_buf(
|
||||
@@ -122,10 +148,15 @@ where
|
||||
_position: &mut Position,
|
||||
_escape: bool,
|
||||
_mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
if let Some(meta) = use_context::<ServerMetaContext>() {
|
||||
let mut buf = String::new();
|
||||
_ = html::attributes_to_html(self.attributes, &mut buf);
|
||||
_ = html::attributes_to_html(
|
||||
self.attributes,
|
||||
extra_attrs,
|
||||
&mut buf,
|
||||
);
|
||||
if !buf.is_empty() {
|
||||
_ = meta.html.send(buf);
|
||||
}
|
||||
@@ -136,14 +167,30 @@ where
|
||||
self,
|
||||
_cursor: &Cursor,
|
||||
_position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
let el = document()
|
||||
.document_element()
|
||||
.expect("there to be a <html> element");
|
||||
|
||||
let attributes = self.attributes.hydrate::<FROM_SERVER>(&el);
|
||||
let extra_attrs = extra_attrs.map(|attrs| {
|
||||
attrs
|
||||
.into_iter()
|
||||
.map(|attr| attr.hydrate::<FROM_SERVER>(&el))
|
||||
.collect()
|
||||
});
|
||||
|
||||
HtmlViewState { attributes }
|
||||
HtmlViewState {
|
||||
attributes,
|
||||
extra_attrs,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
HtmlView {
|
||||
attributes: self.attributes.into_cloneable_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
use futures::{Stream, StreamExt};
|
||||
use leptos::{
|
||||
attr::NextAttribute,
|
||||
attr::{any_attribute::AnyAttribute, NextAttribute},
|
||||
component,
|
||||
logging::debug_warn,
|
||||
oco::Oco,
|
||||
@@ -57,8 +57,8 @@ use leptos::{
|
||||
},
|
||||
hydration::Cursor,
|
||||
view::{
|
||||
add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
|
||||
RenderHtml,
|
||||
add_attr::AddAnyAttr, any_view::ExtraAttrsMut, Mountable, Position,
|
||||
PositionState, Render, RenderHtml,
|
||||
},
|
||||
},
|
||||
IntoView,
|
||||
@@ -334,6 +334,7 @@ where
|
||||
&mut Position::NextChild,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
);
|
||||
_ = cx.elements.send(buf); // fails only if the receiver is already dropped
|
||||
} else {
|
||||
@@ -390,13 +391,17 @@ where
|
||||
{
|
||||
type State = RegisteredMetaTagState<E, At, Ch>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
let state = self.el.unwrap().build();
|
||||
fn build(self, extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
let state = self.el.unwrap().build(extra_attrs);
|
||||
RegisteredMetaTagState { state }
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
self.el.unwrap().rebuild(&mut state.state);
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
self.el.unwrap().rebuild(&mut state.state, extra_attrs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,14 +434,18 @@ where
|
||||
Ch: RenderHtml + Send,
|
||||
{
|
||||
type AsyncOutput = Self;
|
||||
type Owned = RegisteredMetaTag<E, At::CloneableOwned, Ch::Owned>;
|
||||
|
||||
const MIN_LENGTH: usize = 0;
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
self.el.dry_resolve()
|
||||
fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>) {
|
||||
self.el.dry_resolve(extra_attrs)
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
async fn resolve(
|
||||
self,
|
||||
_extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
self // TODO?
|
||||
}
|
||||
|
||||
@@ -446,6 +455,7 @@ where
|
||||
_position: &mut Position,
|
||||
_escape: bool,
|
||||
_mark_branches: bool,
|
||||
_extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
// meta tags are rendered into the buffer stored into the context
|
||||
// the value has already been taken out, when we're on the server
|
||||
@@ -455,6 +465,7 @@ where
|
||||
self,
|
||||
_cursor: &Cursor,
|
||||
_position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
let cursor = use_context::<MetaContext>()
|
||||
.expect(
|
||||
@@ -465,9 +476,16 @@ where
|
||||
let state = self.el.unwrap().hydrate::<FROM_SERVER>(
|
||||
&cursor,
|
||||
&PositionState::new(Position::NextChild),
|
||||
extra_attrs,
|
||||
);
|
||||
RegisteredMetaTagState { state }
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
RegisteredMetaTag {
|
||||
el: self.el.map(|inner| inner.into_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, At, Ch> Mountable for RegisteredMetaTagState<E, At, Ch>
|
||||
@@ -520,9 +538,14 @@ struct MetaTagsView;
|
||||
impl Render for MetaTagsView {
|
||||
type State = ();
|
||||
|
||||
fn build(self) -> Self::State {}
|
||||
fn build(self, _extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {}
|
||||
|
||||
fn rebuild(self, _state: &mut Self::State) {}
|
||||
fn rebuild(
|
||||
self,
|
||||
_state: &mut Self::State,
|
||||
_extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAnyAttr for MetaTagsView {
|
||||
@@ -541,12 +564,16 @@ impl AddAnyAttr for MetaTagsView {
|
||||
|
||||
impl RenderHtml for MetaTagsView {
|
||||
type AsyncOutput = Self;
|
||||
type Owned = Self;
|
||||
|
||||
const MIN_LENGTH: usize = 0;
|
||||
|
||||
fn dry_resolve(&mut self) {}
|
||||
fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
async fn resolve(
|
||||
self,
|
||||
_extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
self
|
||||
}
|
||||
|
||||
@@ -556,6 +583,7 @@ impl RenderHtml for MetaTagsView {
|
||||
_position: &mut Position,
|
||||
_escape: bool,
|
||||
_mark_branches: bool,
|
||||
_extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
buf.push_str("<!--HEAD-->");
|
||||
}
|
||||
@@ -564,8 +592,13 @@ impl RenderHtml for MetaTagsView {
|
||||
self,
|
||||
_cursor: &Cursor,
|
||||
_position: &PositionState,
|
||||
_extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait OrDefaultNonce {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{use_head, MetaContext, ServerMetaContext};
|
||||
use leptos::{
|
||||
attr::Attribute,
|
||||
attr::{any_attribute::AnyAttribute, Attribute},
|
||||
component,
|
||||
oco::Oco,
|
||||
reactive::{
|
||||
@@ -11,8 +11,8 @@ use leptos::{
|
||||
dom::document,
|
||||
hydration::Cursor,
|
||||
view::{
|
||||
add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
|
||||
RenderHtml,
|
||||
add_attr::AddAnyAttr, any_view::ExtraAttrsMut, Mountable, Position,
|
||||
PositionState, Render, RenderHtml,
|
||||
},
|
||||
},
|
||||
text_prop::TextProp,
|
||||
@@ -189,7 +189,7 @@ struct TitleViewState {
|
||||
impl Render for TitleView {
|
||||
type State = TitleViewState;
|
||||
|
||||
fn build(mut self) -> Self::State {
|
||||
fn build(mut self, _extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
let el = self.el();
|
||||
let meta = self.meta;
|
||||
if let Some(formatter) = self.formatter.take() {
|
||||
@@ -213,8 +213,12 @@ impl Render for TitleView {
|
||||
TitleViewState { effect }
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
*state = self.build();
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
*state = self.build(extra_attrs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,12 +238,16 @@ impl AddAnyAttr for TitleView {
|
||||
|
||||
impl RenderHtml for TitleView {
|
||||
type AsyncOutput = Self;
|
||||
type Owned = Self;
|
||||
|
||||
const MIN_LENGTH: usize = 0;
|
||||
|
||||
fn dry_resolve(&mut self) {}
|
||||
fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
async fn resolve(
|
||||
self,
|
||||
_extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
self
|
||||
}
|
||||
|
||||
@@ -249,6 +257,7 @@ impl RenderHtml for TitleView {
|
||||
_position: &mut Position,
|
||||
_escape: bool,
|
||||
_mark_branches: bool,
|
||||
_extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
// meta tags are rendered into the buffer stored into the context
|
||||
// the value has already been taken out, when we're on the server
|
||||
@@ -258,6 +267,7 @@ impl RenderHtml for TitleView {
|
||||
mut self,
|
||||
_cursor: &Cursor,
|
||||
_position: &PositionState,
|
||||
_extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
let el = self.el();
|
||||
let meta = self.meta;
|
||||
@@ -282,6 +292,10 @@ impl RenderHtml for TitleView {
|
||||
});
|
||||
TitleViewState { effect }
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Mountable for TitleViewState {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_router"
|
||||
version = "0.7.4"
|
||||
version = "0.8.0-alpha"
|
||||
authors = ["Greg Johnston", "Ben Wishovich"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::{
|
||||
use any_spawner::Executor;
|
||||
use either_of::Either;
|
||||
use futures::FutureExt;
|
||||
use leptos::attr::any_attribute::AnyAttribute;
|
||||
use reactive_graph::{
|
||||
computed::{ArcMemo, ScopedFuture},
|
||||
owner::{provide_context, Owner},
|
||||
@@ -25,7 +26,7 @@ use tachys::{
|
||||
ssr::StreamBuilder,
|
||||
view::{
|
||||
add_attr::AddAnyAttr,
|
||||
any_view::{AnyView, AnyViewState, IntoAny},
|
||||
any_view::{AnyView, AnyViewState, ExtraAttrsMut, IntoAny},
|
||||
Mountable, Position, PositionState, Render, RenderHtml,
|
||||
},
|
||||
};
|
||||
@@ -79,7 +80,7 @@ where
|
||||
{
|
||||
type State = Rc<RefCell<FlatRoutesViewState>>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
fn build(self, extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
let FlatRoutesView {
|
||||
current_url,
|
||||
routes,
|
||||
@@ -117,7 +118,7 @@ where
|
||||
|
||||
match new_match {
|
||||
None => Rc::new(RefCell::new(FlatRoutesViewState {
|
||||
view: fallback().into_any().build(),
|
||||
view: fallback().into_any().build(extra_attrs),
|
||||
id,
|
||||
owner,
|
||||
params,
|
||||
@@ -150,7 +151,7 @@ where
|
||||
|
||||
match view.as_mut().now_or_never() {
|
||||
Some(view) => Rc::new(RefCell::new(FlatRoutesViewState {
|
||||
view: view.into_any().build(),
|
||||
view: view.into_any().build(extra_attrs),
|
||||
id,
|
||||
owner,
|
||||
params,
|
||||
@@ -161,7 +162,7 @@ where
|
||||
None => {
|
||||
let state =
|
||||
Rc::new(RefCell::new(FlatRoutesViewState {
|
||||
view: ().into_any().build(),
|
||||
view: ().into_any().build(extra_attrs.clone()),
|
||||
id,
|
||||
owner,
|
||||
params,
|
||||
@@ -174,8 +175,10 @@ where
|
||||
let state = Rc::clone(&state);
|
||||
async move {
|
||||
let view = view.await;
|
||||
view.into_any()
|
||||
.rebuild(&mut state.borrow_mut().view);
|
||||
view.into_any().rebuild(
|
||||
&mut state.borrow_mut().view,
|
||||
extra_attrs,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -186,7 +189,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
let FlatRoutesView {
|
||||
current_url,
|
||||
location,
|
||||
@@ -264,7 +271,9 @@ where
|
||||
provide_context(url);
|
||||
provide_context(params_memo);
|
||||
provide_context(Matched(ArcMemo::from(new_matched)));
|
||||
fallback().into_any().rebuild(&mut state.borrow_mut().view)
|
||||
fallback()
|
||||
.into_any()
|
||||
.rebuild(&mut state.borrow_mut().view, extra_attrs)
|
||||
});
|
||||
if let Some(location) = location {
|
||||
location.ready_to_complete();
|
||||
@@ -314,8 +323,10 @@ where
|
||||
== spawned_path
|
||||
{
|
||||
let rebuild = move || {
|
||||
view.into_any()
|
||||
.rebuild(&mut state.borrow_mut().view);
|
||||
view.into_any().rebuild(
|
||||
&mut state.borrow_mut().view,
|
||||
extra_attrs,
|
||||
);
|
||||
};
|
||||
if transition {
|
||||
start_view_transition(0, is_back, rebuild);
|
||||
@@ -343,7 +354,7 @@ impl<Loc, Defs, FalFn, Fal> AddAnyAttr for FlatRoutesView<Loc, Defs, FalFn>
|
||||
where
|
||||
Loc: LocationProvider + Send,
|
||||
Defs: MatchNestedRoutes + Send + 'static,
|
||||
FalFn: FnOnce() -> Fal + Send,
|
||||
FalFn: FnOnce() -> Fal + Send + 'static,
|
||||
Fal: RenderHtml + 'static,
|
||||
{
|
||||
type Output<SomeNewAttr: leptos::attr::Attribute> =
|
||||
@@ -416,16 +427,20 @@ impl<Loc, Defs, FalFn, Fal> RenderHtml for FlatRoutesView<Loc, Defs, FalFn>
|
||||
where
|
||||
Loc: LocationProvider + Send,
|
||||
Defs: MatchNestedRoutes + Send + 'static,
|
||||
FalFn: FnOnce() -> Fal + Send,
|
||||
FalFn: FnOnce() -> Fal + Send + 'static,
|
||||
Fal: RenderHtml + 'static,
|
||||
{
|
||||
type AsyncOutput = Self;
|
||||
type Owned = Self;
|
||||
|
||||
const MIN_LENGTH: usize = <Either<Fal, AnyView> as RenderHtml>::MIN_LENGTH;
|
||||
|
||||
fn dry_resolve(&mut self) {}
|
||||
fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
async fn resolve(
|
||||
self,
|
||||
_extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
self
|
||||
}
|
||||
|
||||
@@ -435,6 +450,7 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
// if this is being run on the server for the first time, generating all possible routes
|
||||
if RouteList::is_generating() {
|
||||
@@ -481,7 +497,13 @@ where
|
||||
RouteList::register(RouteList::from(routes));
|
||||
} else {
|
||||
let view = self.choose_ssr();
|
||||
view.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
view.to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -491,6 +513,7 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -500,6 +523,7 @@ where
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -507,6 +531,7 @@ where
|
||||
self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
// this can be mostly the same as the build() implementation, but with hydrate()
|
||||
//
|
||||
@@ -551,9 +576,11 @@ where
|
||||
|
||||
match new_match {
|
||||
None => Rc::new(RefCell::new(FlatRoutesViewState {
|
||||
view: fallback()
|
||||
.into_any()
|
||||
.hydrate::<FROM_SERVER>(cursor, position),
|
||||
view: fallback().into_any().hydrate::<FROM_SERVER>(
|
||||
cursor,
|
||||
position,
|
||||
extra_attrs,
|
||||
),
|
||||
id,
|
||||
owner,
|
||||
params,
|
||||
@@ -586,9 +613,11 @@ where
|
||||
|
||||
match view.as_mut().now_or_never() {
|
||||
Some(view) => Rc::new(RefCell::new(FlatRoutesViewState {
|
||||
view: view
|
||||
.into_any()
|
||||
.hydrate::<FROM_SERVER>(cursor, position),
|
||||
view: view.into_any().hydrate::<FROM_SERVER>(
|
||||
cursor,
|
||||
position,
|
||||
extra_attrs,
|
||||
),
|
||||
id,
|
||||
owner,
|
||||
params,
|
||||
@@ -604,4 +633,8 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::{PartialPathMatch, PathSegment};
|
||||
use std::sync::Arc;
|
||||
mod param_segments;
|
||||
mod static_segment;
|
||||
mod tuples;
|
||||
@@ -11,9 +12,37 @@ pub use static_segment::*;
|
||||
/// This is a "horizontal" matching: i.e., it treats a tuple of route segments
|
||||
/// as subsequent segments of the URL and tries to match them all.
|
||||
pub trait PossibleRouteMatch {
|
||||
const OPTIONAL: bool = false;
|
||||
fn optional(&self) -> bool;
|
||||
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>>;
|
||||
|
||||
fn generate_path(&self, path: &mut Vec<PathSegment>);
|
||||
}
|
||||
|
||||
impl PossibleRouteMatch for Box<dyn PossibleRouteMatch + Send + Sync> {
|
||||
fn optional(&self) -> bool {
|
||||
(**self).optional()
|
||||
}
|
||||
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
|
||||
(**self).test(path)
|
||||
}
|
||||
|
||||
fn generate_path(&self, path: &mut Vec<PathSegment>) {
|
||||
(**self).generate_path(path);
|
||||
}
|
||||
}
|
||||
|
||||
impl PossibleRouteMatch for Arc<dyn PossibleRouteMatch + Send + Sync> {
|
||||
fn optional(&self) -> bool {
|
||||
(**self).optional()
|
||||
}
|
||||
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
|
||||
(**self).test(path)
|
||||
}
|
||||
|
||||
fn generate_path(&self, path: &mut Vec<PathSegment>) {
|
||||
(**self).generate_path(path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,10 @@ use std::borrow::Cow;
|
||||
pub struct ParamSegment(pub &'static str);
|
||||
|
||||
impl PossibleRouteMatch for ParamSegment {
|
||||
fn optional(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
|
||||
let mut matched_len = 0;
|
||||
let mut param_offset = 0;
|
||||
@@ -121,6 +125,10 @@ impl PossibleRouteMatch for ParamSegment {
|
||||
pub struct WildcardSegment(pub &'static str);
|
||||
|
||||
impl PossibleRouteMatch for WildcardSegment {
|
||||
fn optional(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
|
||||
let mut matched_len = 0;
|
||||
let mut param_offset = 0;
|
||||
@@ -158,7 +166,9 @@ impl PossibleRouteMatch for WildcardSegment {
|
||||
pub struct OptionalParamSegment(pub &'static str);
|
||||
|
||||
impl PossibleRouteMatch for OptionalParamSegment {
|
||||
const OPTIONAL: bool = true;
|
||||
fn optional(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
|
||||
let mut matched_len = 0;
|
||||
|
||||
@@ -2,6 +2,10 @@ use super::{PartialPathMatch, PathSegment, PossibleRouteMatch};
|
||||
use std::fmt::Debug;
|
||||
|
||||
impl PossibleRouteMatch for () {
|
||||
fn optional(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
|
||||
Some(PartialPathMatch::new(path, vec![], ""))
|
||||
}
|
||||
@@ -54,6 +58,10 @@ impl AsPath for &'static str {
|
||||
pub struct StaticSegment<T: AsPath>(pub T);
|
||||
|
||||
impl<T: AsPath> PossibleRouteMatch for StaticSegment<T> {
|
||||
fn optional(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
|
||||
let mut matched_len = 0;
|
||||
let mut test = path.chars().peekable();
|
||||
|
||||
@@ -8,14 +8,20 @@ macro_rules! tuples {
|
||||
$first: PossibleRouteMatch,
|
||||
$($ty: PossibleRouteMatch),*,
|
||||
{
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
|
||||
// on the first run, include all optionals
|
||||
let mut include_optionals = {
|
||||
[$first::OPTIONAL, $($ty::OPTIONAL),*].into_iter().filter(|n| *n).count()
|
||||
};
|
||||
|
||||
fn optional(&self) -> bool {
|
||||
#[allow(non_snake_case)]
|
||||
let ($first, $($ty,)*) = &self;
|
||||
[$first.optional(), $($ty.optional()),*].into_iter().any(|n| n)
|
||||
}
|
||||
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
|
||||
#[allow(non_snake_case)]
|
||||
let ($first, $($ty,)*) = &self;
|
||||
|
||||
// on the first run, include all optionals
|
||||
let mut include_optionals = {
|
||||
[$first.optional(), $($ty.optional()),*].into_iter().filter(|n| *n).count()
|
||||
};
|
||||
|
||||
loop {
|
||||
let mut nth_field = 0;
|
||||
@@ -25,7 +31,7 @@ macro_rules! tuples {
|
||||
let mut p = Vec::new();
|
||||
let mut m = String::new();
|
||||
|
||||
if !$first::OPTIONAL || nth_field < include_optionals {
|
||||
if !$first.optional() || nth_field < include_optionals {
|
||||
match $first.test(r) {
|
||||
None => {
|
||||
return None;
|
||||
@@ -40,16 +46,16 @@ macro_rules! tuples {
|
||||
|
||||
matched_len += m.len();
|
||||
$(
|
||||
if $ty::OPTIONAL {
|
||||
if $ty.optional() {
|
||||
nth_field += 1;
|
||||
}
|
||||
if !$ty::OPTIONAL || nth_field < include_optionals {
|
||||
if !$ty.optional() || nth_field < include_optionals {
|
||||
let PartialPathMatch {
|
||||
remaining,
|
||||
matched,
|
||||
params
|
||||
} = match $ty.test(r) {
|
||||
None => if $ty::OPTIONAL {
|
||||
None => if $ty.optional() {
|
||||
return None;
|
||||
} else {
|
||||
if include_optionals == 0 {
|
||||
@@ -90,6 +96,10 @@ where
|
||||
Self: core::fmt::Debug,
|
||||
A: PossibleRouteMatch,
|
||||
{
|
||||
fn optional(&self) -> bool {
|
||||
self.0.optional()
|
||||
}
|
||||
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
|
||||
let remaining = path;
|
||||
let PartialPathMatch {
|
||||
|
||||
@@ -151,7 +151,7 @@ impl<Segments, Children, Data, View> MatchNestedRoutes
|
||||
for NestedRoute<Segments, Children, Data, View>
|
||||
where
|
||||
Self: 'static,
|
||||
Segments: PossibleRouteMatch + std::fmt::Debug,
|
||||
Segments: PossibleRouteMatch,
|
||||
Children: MatchNestedRoutes,
|
||||
Children::Match: MatchParams,
|
||||
Children: 'static,
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
use any_spawner::Executor;
|
||||
use either_of::{Either, EitherOf3};
|
||||
use futures::{channel::oneshot, future::join_all, FutureExt};
|
||||
use leptos::{component, oco::Oco};
|
||||
use leptos::{attr::any_attribute::AnyAttribute, component, oco::Oco};
|
||||
use or_poisoned::OrPoisoned;
|
||||
use reactive_graph::{
|
||||
computed::{ArcMemo, ScopedFuture},
|
||||
@@ -36,7 +36,7 @@ use tachys::{
|
||||
ssr::StreamBuilder,
|
||||
view::{
|
||||
add_attr::AddAnyAttr,
|
||||
any_view::{AnyView, IntoAny},
|
||||
any_view::{AnyView, ExtraAttrsMut, IntoAny},
|
||||
either::EitherOf3State,
|
||||
Mountable, Position, PositionState, Render, RenderHtml,
|
||||
},
|
||||
@@ -76,7 +76,7 @@ where
|
||||
// TODO support fallback while loading
|
||||
type State = NestedRouteViewState<Fal>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
fn build(self, extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
let NestedRoutesView {
|
||||
routes,
|
||||
outer_owner,
|
||||
@@ -95,7 +95,7 @@ where
|
||||
let new_match = routes.match_route(url.path());
|
||||
|
||||
// start with an empty view because we'll be loading routes async
|
||||
let view = EitherOf3::A(()).build();
|
||||
let view = EitherOf3::A(()).build(extra_attrs.clone());
|
||||
let view = Rc::new(RefCell::new(view));
|
||||
let matched_view = match new_match {
|
||||
None => EitherOf3::B(fallback()),
|
||||
@@ -120,7 +120,7 @@ where
|
||||
for trigger in triggers {
|
||||
trigger.notify();
|
||||
}
|
||||
matched_view.rebuild(&mut *view.borrow_mut());
|
||||
matched_view.rebuild(&mut *view.borrow_mut(), extra_attrs);
|
||||
})
|
||||
});
|
||||
|
||||
@@ -132,7 +132,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
let url_snapshot = self.current_url.get_untracked();
|
||||
|
||||
// if the path is the same, we do not need to re-route
|
||||
@@ -154,7 +158,7 @@ where
|
||||
match new_match {
|
||||
None => {
|
||||
EitherOf3::<(), Fal, AnyView>::B((self.fallback)())
|
||||
.rebuild(&mut state.view.borrow_mut());
|
||||
.rebuild(&mut state.view.borrow_mut(), extra_attrs);
|
||||
state.outlets.clear();
|
||||
if let Some(loc) = self.location {
|
||||
loc.ready_to_complete();
|
||||
@@ -213,7 +217,10 @@ where
|
||||
if matches!(state.view.borrow().state, EitherOf3::B(_)) {
|
||||
self.outer_owner.with(|| {
|
||||
EitherOf3::<(), Fal, AnyView>::C(Outlet().into_any())
|
||||
.rebuild(&mut *state.view.borrow_mut());
|
||||
.rebuild(
|
||||
&mut *state.view.borrow_mut(),
|
||||
extra_attrs,
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -228,8 +235,8 @@ where
|
||||
impl<Loc, Defs, Fal, FalFn> AddAnyAttr for NestedRoutesView<Loc, Defs, FalFn>
|
||||
where
|
||||
Loc: LocationProvider + Send,
|
||||
Defs: MatchNestedRoutes + Send,
|
||||
FalFn: FnOnce() -> Fal + Send,
|
||||
Defs: MatchNestedRoutes + Send + 'static,
|
||||
FalFn: FnOnce() -> Fal + Send + 'static,
|
||||
Fal: RenderHtml + 'static,
|
||||
{
|
||||
type Output<SomeNewAttr: leptos::attr::Attribute> =
|
||||
@@ -249,17 +256,21 @@ where
|
||||
impl<Loc, Defs, FalFn, Fal> RenderHtml for NestedRoutesView<Loc, Defs, FalFn>
|
||||
where
|
||||
Loc: LocationProvider + Send,
|
||||
Defs: MatchNestedRoutes + Send,
|
||||
FalFn: FnOnce() -> Fal + Send,
|
||||
Defs: MatchNestedRoutes + Send + 'static,
|
||||
FalFn: FnOnce() -> Fal + Send + 'static,
|
||||
Fal: RenderHtml + 'static,
|
||||
{
|
||||
type AsyncOutput = Self;
|
||||
type Owned = Self;
|
||||
|
||||
const MIN_LENGTH: usize = 0; // TODO
|
||||
|
||||
fn dry_resolve(&mut self) {}
|
||||
fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
async fn resolve(
|
||||
self,
|
||||
_extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
self
|
||||
}
|
||||
|
||||
@@ -269,6 +280,7 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
// if this is being run on the server for the first time, generating all possible routes
|
||||
if RouteList::is_generating() {
|
||||
@@ -348,7 +360,13 @@ where
|
||||
outer_owner.with(|| Either::Right(Outlet().into_any()))
|
||||
}
|
||||
};
|
||||
view.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
view.to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,6 +376,7 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -400,6 +419,7 @@ where
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -407,6 +427,7 @@ where
|
||||
self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
let NestedRoutesView {
|
||||
routes,
|
||||
@@ -446,7 +467,7 @@ where
|
||||
outer_owner.with(|| EitherOf3::C(Outlet().into_any()))
|
||||
}
|
||||
}
|
||||
.hydrate::<FROM_SERVER>(cursor, position),
|
||||
.hydrate::<FROM_SERVER>(cursor, position, extra_attrs),
|
||||
));
|
||||
|
||||
NestedRouteViewState {
|
||||
@@ -456,6 +477,10 @@ where
|
||||
view,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
type OutletViewFn = Box<dyn Fn() -> Suspend<AnyView> + Send>;
|
||||
|
||||
@@ -89,7 +89,7 @@ where
|
||||
type State =
|
||||
ReactiveRouterInnerState<Rndr, Loc, Defs, FallbackFn, Fallback>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
fn build(self, _extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
let (prev_id, inner) = self.inner.fallback_or_view();
|
||||
let owner = self.owner.with(Owner::new);
|
||||
ReactiveRouterInnerState {
|
||||
@@ -100,7 +100,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
_extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
let (new_id, view) = self.inner.fallback_or_view();
|
||||
if new_id != state.prev_id {
|
||||
state.owner = self.owner.with(Owner::new)
|
||||
@@ -130,6 +134,7 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
_extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
// if this is being run on the server for the first time, generating all possible routes
|
||||
if RouteList::is_generating() {
|
||||
@@ -156,6 +161,7 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
_extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -169,6 +175,7 @@ where
|
||||
self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
_extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
let (prev_id, inner) = self.inner.fallback_or_view();
|
||||
let owner = self.owner.with(Owner::new);
|
||||
@@ -280,7 +287,7 @@ where
|
||||
{
|
||||
type State = ReactiveRouteState<View::State>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
fn build(self, extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
let MatchedRoute {
|
||||
search_params,
|
||||
params,
|
||||
@@ -291,14 +298,19 @@ where
|
||||
params: ArcRwSignal::new(params),
|
||||
matched: ArcRwSignal::new(matched),
|
||||
};
|
||||
let view_state = untrack(|| (self.view_fn)(&matched).build());
|
||||
let view_state =
|
||||
untrack(|| (self.view_fn)(&matched).build(extra_attrs.clone()));
|
||||
ReactiveRouteState {
|
||||
matched,
|
||||
view_state,
|
||||
}
|
||||
}
|
||||
|
||||
fn rebuild(mut self, state: &mut Self::State) {
|
||||
fn rebuild(
|
||||
mut self,
|
||||
state: &mut Self::State,
|
||||
_extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
let ReactiveRouteState { matched, .. } = state;
|
||||
matched
|
||||
.search_params
|
||||
@@ -324,6 +336,7 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
let MatchedRoute {
|
||||
search_params,
|
||||
@@ -336,7 +349,12 @@ where
|
||||
matched: ArcRwSignal::new(matched),
|
||||
};
|
||||
untrack(|| {
|
||||
(self.view_fn)(&matched).to_html_with_buf(buf, position, escape)
|
||||
(self.view_fn)(&matched).to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
extra_attrs.clone(),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -346,6 +364,7 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -360,8 +379,12 @@ where
|
||||
matched: ArcRwSignal::new(matched),
|
||||
};
|
||||
untrack(|| {
|
||||
(self.view_fn)(&matched)
|
||||
.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape)
|
||||
(self.view_fn)(&matched).to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
extra_attrs.clone(),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -369,6 +392,7 @@ where
|
||||
self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
let MatchedRoute {
|
||||
search_params,
|
||||
@@ -381,7 +405,11 @@ where
|
||||
matched: ArcRwSignal::new(matched),
|
||||
};
|
||||
let view_state = untrack(|| {
|
||||
(self.view_fn)(&matched).hydrate::<FROM_SERVER>(cursor, position)
|
||||
(self.view_fn)(&matched).hydrate::<FROM_SERVER>(
|
||||
cursor,
|
||||
position,
|
||||
extra_attrs.clone(),
|
||||
)
|
||||
});
|
||||
ReactiveRouteState {
|
||||
matched,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_router_macro"
|
||||
version = "0.7.4"
|
||||
version = "0.8.0-alpha"
|
||||
authors = ["Greg Johnston", "Ben Wishovich"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -14,6 +14,7 @@ throw_error = { workspace = true }
|
||||
server_fn_macro_default = { workspace = true }
|
||||
# used for hashing paths in #[server] macro
|
||||
const_format = "0.2.33"
|
||||
const-str = "0.5.7"
|
||||
xxhash-rust = { version = "0.8.12", features = ["const_xxh64"] }
|
||||
# used across multiple features
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
@@ -30,7 +31,7 @@ once_cell = "1.20"
|
||||
actix-web = { version = "4.9", optional = true }
|
||||
|
||||
# axum
|
||||
axum = { version = "0.7.9", optional = true, default-features = false, features = [
|
||||
axum = { version = "0.8.1", optional = true, default-features = false, features = [
|
||||
"multipart",
|
||||
] }
|
||||
tower = { version = "0.5.1", optional = true }
|
||||
@@ -53,6 +54,7 @@ bytes = "1.9"
|
||||
http-body-util = { version = "0.1.2", optional = true }
|
||||
rkyv = { version = "0.8.9", optional = true }
|
||||
rmp-serde = { version = "1.3.0", optional = true }
|
||||
base64 = { version = "0.22.1" }
|
||||
|
||||
# client
|
||||
gloo-net = { version = "0.6.0", optional = true }
|
||||
@@ -231,4 +233,4 @@ skip_feature_sets = [
|
||||
]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
|
||||
@@ -74,7 +74,7 @@ pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
args.into(),
|
||||
s.into(),
|
||||
Some(syn::parse_quote!(server_fns)),
|
||||
"/api",
|
||||
option_env!("SERVER_FN_PREFIX").unwrap_or("/api"),
|
||||
None,
|
||||
None,
|
||||
) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{error::ServerFnError, request::ClientReq, response::ClientRes};
|
||||
use crate::{request::ClientReq, response::ClientRes};
|
||||
use std::{future::Future, sync::OnceLock};
|
||||
|
||||
static ROOT_URL: OnceLock<&'static str> = OnceLock::new();
|
||||
@@ -21,16 +21,16 @@ pub fn get_server_url() -> &'static str {
|
||||
/// This trait is implemented for things like a browser `fetch` request or for
|
||||
/// the `reqwest` trait. It should almost never be necessary to implement it
|
||||
/// yourself, unless you’re trying to use an alternative HTTP crate on the client side.
|
||||
pub trait Client<CustErr> {
|
||||
pub trait Client<E> {
|
||||
/// The type of a request sent by this client.
|
||||
type Request: ClientReq<CustErr> + Send;
|
||||
type Request: ClientReq<E> + Send;
|
||||
/// The type of a response received by this client.
|
||||
type Response: ClientRes<CustErr> + Send;
|
||||
type Response: ClientRes<E> + Send;
|
||||
|
||||
/// Sends the request and receives a response.
|
||||
fn send(
|
||||
req: Self::Request,
|
||||
) -> impl Future<Output = Result<Self::Response, ServerFnError<CustErr>>> + Send;
|
||||
) -> impl Future<Output = Result<Self::Response, E>> + Send;
|
||||
}
|
||||
|
||||
#[cfg(feature = "browser")]
|
||||
@@ -38,24 +38,23 @@ pub trait Client<CustErr> {
|
||||
pub mod browser {
|
||||
use super::Client;
|
||||
use crate::{
|
||||
error::ServerFnError,
|
||||
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
|
||||
request::browser::{BrowserRequest, RequestInner},
|
||||
response::browser::BrowserResponse,
|
||||
};
|
||||
use send_wrapper::SendWrapper;
|
||||
use std::future::Future;
|
||||
|
||||
/// Implements [`Client`] for a `fetch` request in the browser.
|
||||
/// Implements [`Client`] for a `fetch` request in the browser.
|
||||
pub struct BrowserClient;
|
||||
|
||||
impl<CustErr> Client<CustErr> for BrowserClient {
|
||||
impl<E: FromServerFnError> Client<E> for BrowserClient {
|
||||
type Request = BrowserRequest;
|
||||
type Response = BrowserResponse;
|
||||
|
||||
fn send(
|
||||
req: Self::Request,
|
||||
) -> impl Future<Output = Result<Self::Response, ServerFnError<CustErr>>>
|
||||
+ Send {
|
||||
) -> impl Future<Output = Result<Self::Response, E>> + Send {
|
||||
SendWrapper::new(async move {
|
||||
let req = req.0.take();
|
||||
let RequestInner {
|
||||
@@ -66,7 +65,10 @@ pub mod browser {
|
||||
.send()
|
||||
.await
|
||||
.map(|res| BrowserResponse(SendWrapper::new(res)))
|
||||
.map_err(|e| ServerFnError::Request(e.to_string()));
|
||||
.map_err(|e| {
|
||||
ServerFnErrorErr::Request(e.to_string())
|
||||
.into_app_error()
|
||||
});
|
||||
|
||||
// at this point, the future has successfully resolved without being dropped, so we
|
||||
// can prevent the `AbortController` from firing
|
||||
@@ -83,7 +85,10 @@ pub mod browser {
|
||||
/// Implements [`Client`] for a request made by [`reqwest`].
|
||||
pub mod reqwest {
|
||||
use super::Client;
|
||||
use crate::{error::ServerFnError, request::reqwest::CLIENT};
|
||||
use crate::{
|
||||
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
|
||||
request::reqwest::CLIENT,
|
||||
};
|
||||
use futures::TryFutureExt;
|
||||
use reqwest::{Request, Response};
|
||||
use std::future::Future;
|
||||
@@ -91,17 +96,16 @@ pub mod reqwest {
|
||||
/// Implements [`Client`] for a request made by [`reqwest`].
|
||||
pub struct ReqwestClient;
|
||||
|
||||
impl<CustErr> Client<CustErr> for ReqwestClient {
|
||||
impl<E: FromServerFnError> Client<E> for ReqwestClient {
|
||||
type Request = Request;
|
||||
type Response = Response;
|
||||
|
||||
fn send(
|
||||
req: Self::Request,
|
||||
) -> impl Future<Output = Result<Self::Response, ServerFnError<CustErr>>>
|
||||
+ Send {
|
||||
CLIENT
|
||||
.execute(req)
|
||||
.map_err(|e| ServerFnError::Request(e.to_string()))
|
||||
) -> impl Future<Output = Result<Self::Response, E>> + Send {
|
||||
CLIENT.execute(req).map_err(|e| {
|
||||
ServerFnErrorErr::Request(e.to_string()).into_app_error()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes};
|
||||
use crate::{
|
||||
error::ServerFnError,
|
||||
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
|
||||
request::{ClientReq, Req},
|
||||
response::{ClientRes, Res},
|
||||
response::{ClientRes, TryRes},
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use http::Method;
|
||||
@@ -16,19 +16,17 @@ impl Encoding for Cbor {
|
||||
const METHOD: Method = Method::POST;
|
||||
}
|
||||
|
||||
impl<CustErr, T, Request> IntoReq<Cbor, Request, CustErr> for T
|
||||
impl<E, T, Request> IntoReq<Cbor, Request, E> for T
|
||||
where
|
||||
Request: ClientReq<CustErr>,
|
||||
Request: ClientReq<E>,
|
||||
T: Serialize + Send,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
fn into_req(
|
||||
self,
|
||||
path: &str,
|
||||
accepts: &str,
|
||||
) -> Result<Request, ServerFnError<CustErr>> {
|
||||
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
|
||||
let mut buffer: Vec<u8> = Vec::new();
|
||||
ciborium::ser::into_writer(&self, &mut buffer)
|
||||
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
|
||||
ciborium::ser::into_writer(&self, &mut buffer).map_err(|e| {
|
||||
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
|
||||
})?;
|
||||
Request::try_new_post_bytes(
|
||||
path,
|
||||
accepts,
|
||||
@@ -38,40 +36,44 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, T, Request> FromReq<Cbor, Request, CustErr> for T
|
||||
impl<E, T, Request> FromReq<Cbor, Request, E> for T
|
||||
where
|
||||
Request: Req<CustErr> + Send + 'static,
|
||||
Request: Req<E> + Send + 'static,
|
||||
T: DeserializeOwned,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn from_req(req: Request) -> Result<Self, ServerFnError<CustErr>> {
|
||||
async fn from_req(req: Request) -> Result<Self, E> {
|
||||
let body_bytes = req.try_into_bytes().await?;
|
||||
ciborium::de::from_reader(body_bytes.as_ref())
|
||||
.map_err(|e| ServerFnError::Args(e.to_string()))
|
||||
.map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error())
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, T, Response> IntoRes<Cbor, Response, CustErr> for T
|
||||
impl<E, T, Response> IntoRes<Cbor, Response, E> for T
|
||||
where
|
||||
Response: Res<CustErr>,
|
||||
Response: TryRes<E>,
|
||||
T: Serialize + Send,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn into_res(self) -> Result<Response, ServerFnError<CustErr>> {
|
||||
async fn into_res(self) -> Result<Response, E> {
|
||||
let mut buffer: Vec<u8> = Vec::new();
|
||||
ciborium::ser::into_writer(&self, &mut buffer)
|
||||
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
|
||||
ciborium::ser::into_writer(&self, &mut buffer).map_err(|e| {
|
||||
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
|
||||
})?;
|
||||
Response::try_from_bytes(Cbor::CONTENT_TYPE, Bytes::from(buffer))
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, T, Response> FromRes<Cbor, Response, CustErr> for T
|
||||
impl<E, T, Response> FromRes<Cbor, Response, E> for T
|
||||
where
|
||||
Response: ClientRes<CustErr> + Send,
|
||||
Response: ClientRes<E> + Send,
|
||||
T: DeserializeOwned + Send,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn from_res(res: Response) -> Result<Self, ServerFnError<CustErr>> {
|
||||
async fn from_res(res: Response) -> Result<Self, E> {
|
||||
let data = res.try_into_bytes().await?;
|
||||
ciborium::de::from_reader(data.as_ref())
|
||||
.map_err(|e| ServerFnError::Args(e.to_string()))
|
||||
.map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,20 +116,20 @@ where
|
||||
<ResponseBody as HttpBody>::Data: Send ,
|
||||
<RequestBody as HttpBody>::Data: Send ,
|
||||
{
|
||||
async fn from_req(req: http::Request<RequestBody>) -> Result<Self, ServerFnError<CustErr>> {
|
||||
async fn from_req(req: http::Request<RequestBody>) -> Result<Self, ServerFnError<E>> {
|
||||
let (_parts, body) = req.into_parts();
|
||||
|
||||
let body_bytes = body
|
||||
.collect()
|
||||
.await
|
||||
.map(|c| c.to_bytes())
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))?;
|
||||
.map_err(|e| ServerFnErrorErr::Deserialization(e.to_string()).into())?;
|
||||
let data = ciborium::de::from_reader(body_bytes.as_ref())
|
||||
.map_err(|e| ServerFnError::Args(e.to_string()))?;
|
||||
.map_err(|e| ServerFnErrorErr::Args(e.to_string()).into())?;
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
async fn into_req(self) -> Result<http::Request<Body>, ServerFnError<CustErr>> {
|
||||
async fn into_req(self) -> Result<http::Request<Body>, ServerFnError<E>> {
|
||||
let mut buffer: Vec<u8> = Vec::new();
|
||||
ciborium::ser::into_writer(&self, &mut buffer)?;
|
||||
let req = http::Request::builder()
|
||||
@@ -139,17 +141,17 @@ where
|
||||
.body(Body::from(buffer))?;
|
||||
Ok(req)
|
||||
}
|
||||
async fn from_res(res: http::Response<ResponseBody>) -> Result<Self, ServerFnError<CustErr>> {
|
||||
async fn from_res(res: http::Response<ResponseBody>) -> Result<Self, ServerFnError<E>> {
|
||||
let (_parts, body) = res.into_parts();
|
||||
|
||||
let body_bytes = body
|
||||
.collect()
|
||||
.await
|
||||
.map(|c| c.to_bytes())
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))?;
|
||||
.map_err(|e| ServerFnErrorErr::Deserialization(e.to_string()).into())?;
|
||||
|
||||
ciborium::de::from_reader(body_bytes.as_ref())
|
||||
.map_err(|e| ServerFnError::Args(e.to_string()))
|
||||
.map_err(|e| ServerFnErrorErr::Args(e.to_string()).into())
|
||||
}
|
||||
|
||||
async fn into_res(self) -> http::Response<Body> {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use super::{Encoding, FromReq, FromRes, Streaming};
|
||||
use crate::{
|
||||
error::{NoCustomError, ServerFnError},
|
||||
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
|
||||
request::{ClientReq, Req},
|
||||
response::{ClientRes, Res},
|
||||
response::{ClientRes, TryRes},
|
||||
IntoReq, IntoRes,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
@@ -18,55 +18,58 @@ impl Encoding for Json {
|
||||
const METHOD: Method = Method::POST;
|
||||
}
|
||||
|
||||
impl<CustErr, T, Request> IntoReq<Json, Request, CustErr> for T
|
||||
impl<E, T, Request> IntoReq<Json, Request, E> for T
|
||||
where
|
||||
Request: ClientReq<CustErr>,
|
||||
Request: ClientReq<E>,
|
||||
T: Serialize + Send,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
fn into_req(
|
||||
self,
|
||||
path: &str,
|
||||
accepts: &str,
|
||||
) -> Result<Request, ServerFnError<CustErr>> {
|
||||
let data = serde_json::to_string(&self)
|
||||
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
|
||||
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
|
||||
let data = serde_json::to_string(&self).map_err(|e| {
|
||||
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
|
||||
})?;
|
||||
Request::try_new_post(path, accepts, Json::CONTENT_TYPE, data)
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, T, Request> FromReq<Json, Request, CustErr> for T
|
||||
impl<E, T, Request> FromReq<Json, Request, E> for T
|
||||
where
|
||||
Request: Req<CustErr> + Send + 'static,
|
||||
Request: Req<E> + Send + 'static,
|
||||
T: DeserializeOwned,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn from_req(req: Request) -> Result<Self, ServerFnError<CustErr>> {
|
||||
async fn from_req(req: Request) -> Result<Self, E> {
|
||||
let string_data = req.try_into_string().await?;
|
||||
serde_json::from_str::<Self>(&string_data)
|
||||
.map_err(|e| ServerFnError::Args(e.to_string()))
|
||||
.map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error())
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, T, Response> IntoRes<Json, Response, CustErr> for T
|
||||
impl<E, T, Response> IntoRes<Json, Response, E> for T
|
||||
where
|
||||
Response: Res<CustErr>,
|
||||
Response: TryRes<E>,
|
||||
T: Serialize + Send,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn into_res(self) -> Result<Response, ServerFnError<CustErr>> {
|
||||
let data = serde_json::to_string(&self)
|
||||
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
|
||||
async fn into_res(self) -> Result<Response, E> {
|
||||
let data = serde_json::to_string(&self).map_err(|e| {
|
||||
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
|
||||
})?;
|
||||
Response::try_from_string(Json::CONTENT_TYPE, data)
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, T, Response> FromRes<Json, Response, CustErr> for T
|
||||
impl<E, T, Response> FromRes<Json, Response, E> for T
|
||||
where
|
||||
Response: ClientRes<CustErr> + Send,
|
||||
Response: ClientRes<E> + Send,
|
||||
T: DeserializeOwned + Send,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn from_res(res: Response) -> Result<Self, ServerFnError<CustErr>> {
|
||||
async fn from_res(res: Response) -> Result<Self, E> {
|
||||
let data = res.try_into_string().await?;
|
||||
serde_json::from_str(&data)
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
serde_json::from_str(&data).map_err(|e| {
|
||||
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,35 +105,31 @@ impl Encoding for StreamingJson {
|
||||
/// end before the output will begin.
|
||||
///
|
||||
/// Streaming requests are only allowed over HTTP2 or HTTP3.
|
||||
pub struct JsonStream<T, CustErr = NoCustomError>(
|
||||
Pin<Box<dyn Stream<Item = Result<T, ServerFnError<CustErr>>> + Send>>,
|
||||
);
|
||||
pub struct JsonStream<T, E>(Pin<Box<dyn Stream<Item = Result<T, E>> + Send>>);
|
||||
|
||||
impl<T, CustErr> std::fmt::Debug for JsonStream<T, CustErr> {
|
||||
impl<T, E> std::fmt::Debug for JsonStream<T, E> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("JsonStream").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> JsonStream<T> {
|
||||
impl<T, E> JsonStream<T, E> {
|
||||
/// Creates a new `ByteStream` from the given stream.
|
||||
pub fn new(
|
||||
value: impl Stream<Item = Result<T, ServerFnError>> + Send + 'static,
|
||||
value: impl Stream<Item = Result<T, E>> + Send + 'static,
|
||||
) -> Self {
|
||||
Self(Box::pin(value.map(|value| value)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, CustErr> JsonStream<T, CustErr> {
|
||||
impl<T, E> JsonStream<T, E> {
|
||||
/// Consumes the wrapper, returning a stream of text.
|
||||
pub fn into_inner(
|
||||
self,
|
||||
) -> impl Stream<Item = Result<T, ServerFnError<CustErr>>> + Send {
|
||||
pub fn into_inner(self) -> impl Stream<Item = Result<T, E>> + Send {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T: 'static, CustErr: 'static> From<S> for JsonStream<T, CustErr>
|
||||
impl<S, T: 'static, E: 'static> From<S> for JsonStream<T, E>
|
||||
where
|
||||
S: Stream<Item = T> + Send + 'static,
|
||||
{
|
||||
@@ -139,18 +138,15 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, S, T, Request> IntoReq<StreamingJson, Request, CustErr> for S
|
||||
impl<E, S, T, Request> IntoReq<StreamingJson, Request, E> for S
|
||||
where
|
||||
Request: ClientReq<CustErr>,
|
||||
Request: ClientReq<E>,
|
||||
S: Stream<Item = T> + Send + 'static,
|
||||
T: Serialize + 'static,
|
||||
E: FromServerFnError + Serialize,
|
||||
{
|
||||
fn into_req(
|
||||
self,
|
||||
path: &str,
|
||||
accepts: &str,
|
||||
) -> Result<Request, ServerFnError<CustErr>> {
|
||||
let data: JsonStream<T> = self.into();
|
||||
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
|
||||
let data: JsonStream<T, E> = self.into();
|
||||
Request::try_new_streaming(
|
||||
path,
|
||||
accepts,
|
||||
@@ -164,56 +160,61 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, T, S, Request> FromReq<StreamingJson, Request, CustErr> for S
|
||||
impl<E, T, S, Request> FromReq<StreamingJson, Request, E> for S
|
||||
where
|
||||
Request: Req<CustErr> + Send + 'static,
|
||||
Request: Req<E> + Send + 'static,
|
||||
// The additional `Stream<Item = T>` bound is never used, but it is required to avoid an error where `T` is unconstrained
|
||||
S: Stream<Item = T> + From<JsonStream<T>> + Send + 'static,
|
||||
S: Stream<Item = T> + From<JsonStream<T, E>> + Send + 'static,
|
||||
T: DeserializeOwned + 'static,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn from_req(req: Request) -> Result<Self, ServerFnError<CustErr>> {
|
||||
async fn from_req(req: Request) -> Result<Self, E> {
|
||||
let data = req.try_into_stream()?;
|
||||
let s = JsonStream::new(data.map(|chunk| {
|
||||
chunk.and_then(|bytes| {
|
||||
serde_json::from_slice(bytes.as_ref())
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
serde_json::from_slice(bytes.as_ref()).map_err(|e| {
|
||||
ServerFnErrorErr::Deserialization(e.to_string())
|
||||
.into_app_error()
|
||||
})
|
||||
})
|
||||
}));
|
||||
Ok(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, T, Response> IntoRes<StreamingJson, Response, CustErr>
|
||||
for JsonStream<T, CustErr>
|
||||
impl<E, T, Response> IntoRes<StreamingJson, Response, E> for JsonStream<T, E>
|
||||
where
|
||||
Response: Res<CustErr>,
|
||||
CustErr: 'static,
|
||||
Response: TryRes<E>,
|
||||
T: Serialize + 'static,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn into_res(self) -> Result<Response, ServerFnError<CustErr>> {
|
||||
async fn into_res(self) -> Result<Response, E> {
|
||||
Response::try_from_stream(
|
||||
Streaming::CONTENT_TYPE,
|
||||
self.into_inner().map(|value| {
|
||||
serde_json::to_vec(&value?)
|
||||
.map(Bytes::from)
|
||||
.map_err(|e| ServerFnError::Serialization(e.to_string()))
|
||||
serde_json::to_vec(&value?).map(Bytes::from).map_err(|e| {
|
||||
ServerFnErrorErr::Serialization(e.to_string())
|
||||
.into_app_error()
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, T, Response> FromRes<StreamingJson, Response, CustErr>
|
||||
for JsonStream<T>
|
||||
impl<E, T, Response> FromRes<StreamingJson, Response, E> for JsonStream<T, E>
|
||||
where
|
||||
Response: ClientRes<CustErr> + Send,
|
||||
Response: ClientRes<E> + Send,
|
||||
T: DeserializeOwned,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn from_res(res: Response) -> Result<Self, ServerFnError<CustErr>> {
|
||||
async fn from_res(res: Response) -> Result<Self, E> {
|
||||
let stream = res.try_into_stream()?;
|
||||
Ok(JsonStream::new(stream.map(|chunk| {
|
||||
chunk.and_then(|bytes| {
|
||||
serde_json::from_slice(bytes.as_ref())
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
serde_json::from_slice(bytes.as_ref()).map_err(|e| {
|
||||
ServerFnErrorErr::Deserialization(e.to_string())
|
||||
.into_app_error()
|
||||
})
|
||||
})
|
||||
})))
|
||||
}
|
||||
|
||||
@@ -55,7 +55,6 @@ mod postcard;
|
||||
pub use postcard::*;
|
||||
|
||||
mod stream;
|
||||
use crate::error::ServerFnError;
|
||||
use futures::Future;
|
||||
use http::Method;
|
||||
pub use stream::*;
|
||||
@@ -71,31 +70,27 @@ pub use stream::*;
|
||||
/// For example, here’s the implementation for [`Json`].
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// impl<CustErr, T, Request> IntoReq<Json, Request, CustErr> for T
|
||||
/// impl<E, T, Request> IntoReq<Json, Request, E> for T
|
||||
/// where
|
||||
/// Request: ClientReq<CustErr>,
|
||||
/// Request: ClientReq<E>,
|
||||
/// T: Serialize + Send,
|
||||
/// {
|
||||
/// fn into_req(
|
||||
/// self,
|
||||
/// path: &str,
|
||||
/// accepts: &str,
|
||||
/// ) -> Result<Request, ServerFnError<CustErr>> {
|
||||
/// ) -> Result<Request, E> {
|
||||
/// // try to serialize the data
|
||||
/// let data = serde_json::to_string(&self)
|
||||
/// .map_err(|e| ServerFnError::Serialization(e.to_string()))?;
|
||||
/// .map_err(|e| ServerFnErrorErr::Serialization(e.to_string()).into_app_error())?;
|
||||
/// // and use it as the body of a POST request
|
||||
/// Request::try_new_post(path, accepts, Json::CONTENT_TYPE, data)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait IntoReq<Encoding, Request, CustErr> {
|
||||
pub trait IntoReq<Encoding, Request, E> {
|
||||
/// Attempts to serialize the arguments into an HTTP request.
|
||||
fn into_req(
|
||||
self,
|
||||
path: &str,
|
||||
accepts: &str,
|
||||
) -> Result<Request, ServerFnError<CustErr>>;
|
||||
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E>;
|
||||
}
|
||||
|
||||
/// Deserializes an HTTP request into the data type, on the server.
|
||||
@@ -109,32 +104,31 @@ pub trait IntoReq<Encoding, Request, CustErr> {
|
||||
/// For example, here’s the implementation for [`Json`].
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// impl<CustErr, T, Request> FromReq<Json, Request, CustErr> for T
|
||||
/// impl<E, T, Request> FromReq<Json, Request, E> for T
|
||||
/// where
|
||||
/// // require the Request implement `Req`
|
||||
/// Request: Req<CustErr> + Send + 'static,
|
||||
/// Request: Req<E> + Send + 'static,
|
||||
/// // require that the type can be deserialized with `serde`
|
||||
/// T: DeserializeOwned,
|
||||
/// E: FromServerFnError,
|
||||
/// {
|
||||
/// async fn from_req(
|
||||
/// req: Request,
|
||||
/// ) -> Result<Self, ServerFnError<CustErr>> {
|
||||
/// ) -> Result<Self, E> {
|
||||
/// // try to convert the body of the request into a `String`
|
||||
/// let string_data = req.try_into_string().await?;
|
||||
/// // deserialize the data
|
||||
/// serde_json::from_str::<Self>(&string_data)
|
||||
/// .map_err(|e| ServerFnError::Args(e.to_string()))
|
||||
/// serde_json::from_str(&string_data)
|
||||
/// .map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error())
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait FromReq<Encoding, Request, CustErr>
|
||||
pub trait FromReq<Encoding, Request, E>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
/// Attempts to deserialize the arguments from a request.
|
||||
fn from_req(
|
||||
req: Request,
|
||||
) -> impl Future<Output = Result<Self, ServerFnError<CustErr>>> + Send;
|
||||
fn from_req(req: Request) -> impl Future<Output = Result<Self, E>> + Send;
|
||||
}
|
||||
|
||||
/// Serializes the data type into an HTTP response.
|
||||
@@ -148,25 +142,24 @@ where
|
||||
/// For example, here’s the implementation for [`Json`].
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// impl<CustErr, T, Response> IntoRes<Json, Response, CustErr> for T
|
||||
/// impl<E, T, Response> IntoRes<Json, Response, E> for T
|
||||
/// where
|
||||
/// Response: Res<CustErr>,
|
||||
/// Response: Res<E>,
|
||||
/// T: Serialize + Send,
|
||||
/// E: FromServerFnError,
|
||||
/// {
|
||||
/// async fn into_res(self) -> Result<Response, ServerFnError<CustErr>> {
|
||||
/// async fn into_res(self) -> Result<Response, E> {
|
||||
/// // try to serialize the data
|
||||
/// let data = serde_json::to_string(&self)
|
||||
/// .map_err(|e| ServerFnError::Serialization(e.to_string()))?;
|
||||
/// .map_err(|e| ServerFnErrorErr::Serialization(e.to_string()).into())?;
|
||||
/// // and use it as the body of a response
|
||||
/// Response::try_from_string(Json::CONTENT_TYPE, data)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait IntoRes<Encoding, Response, CustErr> {
|
||||
pub trait IntoRes<Encoding, Response, E> {
|
||||
/// Attempts to serialize the output into an HTTP response.
|
||||
fn into_res(
|
||||
self,
|
||||
) -> impl Future<Output = Result<Response, ServerFnError<CustErr>>> + Send;
|
||||
fn into_res(self) -> impl Future<Output = Result<Response, E>> + Send;
|
||||
}
|
||||
|
||||
/// Deserializes the data type from an HTTP response.
|
||||
@@ -181,30 +174,29 @@ pub trait IntoRes<Encoding, Response, CustErr> {
|
||||
/// For example, here’s the implementation for [`Json`].
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// impl<CustErr, T, Response> FromRes<Json, Response, CustErr> for T
|
||||
/// impl<E, T, Response> FromRes<Json, Response, E> for T
|
||||
/// where
|
||||
/// Response: ClientRes<CustErr> + Send,
|
||||
/// Response: ClientRes<E> + Send,
|
||||
/// T: DeserializeOwned + Send,
|
||||
/// E: FromServerFnError,
|
||||
/// {
|
||||
/// async fn from_res(
|
||||
/// res: Response,
|
||||
/// ) -> Result<Self, ServerFnError<CustErr>> {
|
||||
/// ) -> Result<Self, E> {
|
||||
/// // extracts the request body
|
||||
/// let data = res.try_into_string().await?;
|
||||
/// // and tries to deserialize it as JSON
|
||||
/// serde_json::from_str(&data)
|
||||
/// .map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
/// .map_err(|e| ServerFnErrorErr::Deserialization(e.to_string()).into_app_error())
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait FromRes<Encoding, Response, CustErr>
|
||||
pub trait FromRes<Encoding, Response, E>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
/// Attempts to deserialize the outputs from a response.
|
||||
fn from_res(
|
||||
res: Response,
|
||||
) -> impl Future<Output = Result<Self, ServerFnError<CustErr>>> + Send;
|
||||
fn from_res(res: Response) -> impl Future<Output = Result<Self, E>> + Send;
|
||||
}
|
||||
|
||||
/// Defines a particular encoding format, which can be used for serializing or deserializing data.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes};
|
||||
use crate::{
|
||||
error::ServerFnError,
|
||||
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
|
||||
request::{ClientReq, Req},
|
||||
response::{ClientRes, Res},
|
||||
response::{ClientRes, TryRes},
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use http::Method;
|
||||
@@ -16,18 +16,16 @@ impl Encoding for MsgPack {
|
||||
const METHOD: Method = Method::POST;
|
||||
}
|
||||
|
||||
impl<T, Request, Err> IntoReq<MsgPack, Request, Err> for T
|
||||
impl<T, Request, E> IntoReq<MsgPack, Request, E> for T
|
||||
where
|
||||
Request: ClientReq<Err>,
|
||||
Request: ClientReq<E>,
|
||||
T: Serialize,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
fn into_req(
|
||||
self,
|
||||
path: &str,
|
||||
accepts: &str,
|
||||
) -> Result<Request, ServerFnError<Err>> {
|
||||
let data = rmp_serde::to_vec(&self)
|
||||
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
|
||||
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
|
||||
let data = rmp_serde::to_vec(&self).map_err(|e| {
|
||||
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
|
||||
})?;
|
||||
Request::try_new_post_bytes(
|
||||
path,
|
||||
MsgPack::CONTENT_TYPE,
|
||||
@@ -37,38 +35,43 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Request, Err> FromReq<MsgPack, Request, Err> for T
|
||||
impl<T, Request, E> FromReq<MsgPack, Request, E> for T
|
||||
where
|
||||
Request: Req<Err> + Send,
|
||||
Request: Req<E> + Send,
|
||||
T: DeserializeOwned,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn from_req(req: Request) -> Result<Self, ServerFnError<Err>> {
|
||||
async fn from_req(req: Request) -> Result<Self, E> {
|
||||
let data = req.try_into_bytes().await?;
|
||||
rmp_serde::from_slice::<T>(&data)
|
||||
.map_err(|e| ServerFnError::Args(e.to_string()))
|
||||
.map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Response, Err> IntoRes<MsgPack, Response, Err> for T
|
||||
impl<T, Response, E> IntoRes<MsgPack, Response, E> for T
|
||||
where
|
||||
Response: Res<Err>,
|
||||
Response: TryRes<E>,
|
||||
T: Serialize + Send,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn into_res(self) -> Result<Response, ServerFnError<Err>> {
|
||||
let data = rmp_serde::to_vec(&self)
|
||||
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
|
||||
async fn into_res(self) -> Result<Response, E> {
|
||||
let data = rmp_serde::to_vec(&self).map_err(|e| {
|
||||
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
|
||||
})?;
|
||||
Response::try_from_bytes(MsgPack::CONTENT_TYPE, Bytes::from(data))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Response, Err> FromRes<MsgPack, Response, Err> for T
|
||||
impl<T, Response, E> FromRes<MsgPack, Response, E> for T
|
||||
where
|
||||
Response: ClientRes<Err> + Send,
|
||||
Response: ClientRes<E> + Send,
|
||||
T: DeserializeOwned,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn from_res(res: Response) -> Result<Self, ServerFnError<Err>> {
|
||||
async fn from_res(res: Response) -> Result<Self, E> {
|
||||
let data = res.try_into_bytes().await?;
|
||||
rmp_serde::from_slice(&data)
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
rmp_serde::from_slice(&data).map_err(|e| {
|
||||
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::{Encoding, FromReq};
|
||||
use crate::{
|
||||
error::ServerFnError,
|
||||
error::FromServerFnError,
|
||||
request::{browser::BrowserFormData, ClientReq, Req},
|
||||
IntoReq,
|
||||
};
|
||||
@@ -56,16 +56,12 @@ impl From<FormData> for MultipartData {
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, T, Request> IntoReq<MultipartFormData, Request, CustErr> for T
|
||||
impl<E, T, Request> IntoReq<MultipartFormData, Request, E> for T
|
||||
where
|
||||
Request: ClientReq<CustErr, FormData = BrowserFormData>,
|
||||
Request: ClientReq<E, FormData = BrowserFormData>,
|
||||
T: Into<MultipartData>,
|
||||
{
|
||||
fn into_req(
|
||||
self,
|
||||
path: &str,
|
||||
accepts: &str,
|
||||
) -> Result<Request, ServerFnError<CustErr>> {
|
||||
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
|
||||
let multi = self.into();
|
||||
Request::try_new_multipart(
|
||||
path,
|
||||
@@ -75,20 +71,20 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, T, Request> FromReq<MultipartFormData, Request, CustErr> for T
|
||||
impl<E, T, Request> FromReq<MultipartFormData, Request, E> for T
|
||||
where
|
||||
Request: Req<CustErr> + Send + 'static,
|
||||
Request: Req<E> + Send + 'static,
|
||||
T: From<MultipartData>,
|
||||
CustErr: 'static,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn from_req(req: Request) -> Result<Self, ServerFnError<CustErr>> {
|
||||
async fn from_req(req: Request) -> Result<Self, E> {
|
||||
let boundary = req
|
||||
.to_content_type()
|
||||
.and_then(|ct| multer::parse_boundary(ct).ok())
|
||||
.expect("couldn't parse boundary");
|
||||
let stream = req.try_into_stream()?;
|
||||
let data = multer::Multipart::new(
|
||||
stream.map(|data| data.map_err(|e| e.to_string())),
|
||||
stream.map(|data| data.map_err(|e| e.ser())),
|
||||
boundary,
|
||||
);
|
||||
Ok(MultipartData::Server(data).into())
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes};
|
||||
use crate::{
|
||||
error::ServerFnError,
|
||||
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
|
||||
request::{ClientReq, Req},
|
||||
response::{ClientRes, Res},
|
||||
response::{ClientRes, TryRes},
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use http::Method;
|
||||
@@ -16,18 +16,16 @@ impl Encoding for Postcard {
|
||||
const METHOD: Method = Method::POST;
|
||||
}
|
||||
|
||||
impl<T, Request, Err> IntoReq<Postcard, Request, Err> for T
|
||||
impl<T, Request, E> IntoReq<Postcard, Request, E> for T
|
||||
where
|
||||
Request: ClientReq<Err>,
|
||||
Request: ClientReq<E>,
|
||||
T: Serialize,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
fn into_req(
|
||||
self,
|
||||
path: &str,
|
||||
accepts: &str,
|
||||
) -> Result<Request, ServerFnError<Err>> {
|
||||
let data = postcard::to_allocvec(&self)
|
||||
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
|
||||
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
|
||||
let data = postcard::to_allocvec(&self).map_err(|e| {
|
||||
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
|
||||
})?;
|
||||
Request::try_new_post_bytes(
|
||||
path,
|
||||
Postcard::CONTENT_TYPE,
|
||||
@@ -37,38 +35,43 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Request, Err> FromReq<Postcard, Request, Err> for T
|
||||
impl<T, Request, E> FromReq<Postcard, Request, E> for T
|
||||
where
|
||||
Request: Req<Err> + Send,
|
||||
Request: Req<E> + Send,
|
||||
T: DeserializeOwned,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn from_req(req: Request) -> Result<Self, ServerFnError<Err>> {
|
||||
async fn from_req(req: Request) -> Result<Self, E> {
|
||||
let data = req.try_into_bytes().await?;
|
||||
postcard::from_bytes::<T>(&data)
|
||||
.map_err(|e| ServerFnError::Args(e.to_string()))
|
||||
.map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Response, Err> IntoRes<Postcard, Response, Err> for T
|
||||
impl<T, Response, E> IntoRes<Postcard, Response, E> for T
|
||||
where
|
||||
Response: Res<Err>,
|
||||
Response: TryRes<E>,
|
||||
T: Serialize + Send,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn into_res(self) -> Result<Response, ServerFnError<Err>> {
|
||||
let data = postcard::to_allocvec(&self)
|
||||
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
|
||||
async fn into_res(self) -> Result<Response, E> {
|
||||
let data = postcard::to_allocvec(&self).map_err(|e| {
|
||||
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
|
||||
})?;
|
||||
Response::try_from_bytes(Postcard::CONTENT_TYPE, Bytes::from(data))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Response, Err> FromRes<Postcard, Response, Err> for T
|
||||
impl<T, Response, E> FromRes<Postcard, Response, E> for T
|
||||
where
|
||||
Response: ClientRes<Err> + Send,
|
||||
Response: ClientRes<E> + Send,
|
||||
T: DeserializeOwned,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn from_res(res: Response) -> Result<Self, ServerFnError<Err>> {
|
||||
async fn from_res(res: Response) -> Result<Self, E> {
|
||||
let data = res.try_into_bytes().await?;
|
||||
postcard::from_bytes(&data)
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
postcard::from_bytes(&data).map_err(|e| {
|
||||
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes};
|
||||
use crate::{
|
||||
error::ServerFnError,
|
||||
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
|
||||
request::{ClientReq, Req},
|
||||
response::{ClientRes, Res},
|
||||
response::{ClientRes, TryRes},
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use futures::StreamExt;
|
||||
@@ -29,39 +29,38 @@ impl Encoding for Rkyv {
|
||||
const METHOD: Method = Method::POST;
|
||||
}
|
||||
|
||||
impl<CustErr, T, Request> IntoReq<Rkyv, Request, CustErr> for T
|
||||
impl<E, T, Request> IntoReq<Rkyv, Request, E> for T
|
||||
where
|
||||
Request: ClientReq<CustErr>,
|
||||
Request: ClientReq<E>,
|
||||
T: Archive + for<'a> Serialize<RkyvSerializer<'a>>,
|
||||
T::Archived: Deserialize<T, RkyvDeserializer>
|
||||
+ for<'a> CheckBytes<RkyvValidator<'a>>,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
fn into_req(
|
||||
self,
|
||||
path: &str,
|
||||
accepts: &str,
|
||||
) -> Result<Request, ServerFnError<CustErr>> {
|
||||
let encoded = rkyv::to_bytes::<rancor::Error>(&self)
|
||||
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
|
||||
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
|
||||
let encoded = rkyv::to_bytes::<rancor::Error>(&self).map_err(|e| {
|
||||
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
|
||||
})?;
|
||||
let bytes = Bytes::copy_from_slice(encoded.as_ref());
|
||||
Request::try_new_post_bytes(path, accepts, Rkyv::CONTENT_TYPE, bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, T, Request> FromReq<Rkyv, Request, CustErr> for T
|
||||
impl<E, T, Request> FromReq<Rkyv, Request, E> for T
|
||||
where
|
||||
Request: Req<CustErr> + Send + 'static,
|
||||
Request: Req<E> + Send + 'static,
|
||||
T: Archive + for<'a> Serialize<RkyvSerializer<'a>>,
|
||||
T::Archived: Deserialize<T, RkyvDeserializer>
|
||||
+ for<'a> CheckBytes<RkyvValidator<'a>>,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn from_req(req: Request) -> Result<Self, ServerFnError<CustErr>> {
|
||||
async fn from_req(req: Request) -> Result<Self, E> {
|
||||
let mut aligned = AlignedVec::<1024>::new();
|
||||
let mut body_stream = Box::pin(req.try_into_stream()?);
|
||||
while let Some(chunk) = body_stream.next().await {
|
||||
match chunk {
|
||||
Err(e) => {
|
||||
return Err(ServerFnError::Deserialization(e.to_string()))
|
||||
return Err(e);
|
||||
}
|
||||
Ok(bytes) => {
|
||||
for byte in bytes {
|
||||
@@ -71,36 +70,40 @@ where
|
||||
}
|
||||
}
|
||||
rkyv::from_bytes::<T, rancor::Error>(aligned.as_ref())
|
||||
.map_err(|e| ServerFnError::Args(e.to_string()))
|
||||
.map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error())
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, T, Response> IntoRes<Rkyv, Response, CustErr> for T
|
||||
impl<E, T, Response> IntoRes<Rkyv, Response, E> for T
|
||||
where
|
||||
Response: Res<CustErr>,
|
||||
Response: TryRes<E>,
|
||||
T: Send,
|
||||
T: Archive + for<'a> Serialize<RkyvSerializer<'a>>,
|
||||
T::Archived: Deserialize<T, RkyvDeserializer>
|
||||
+ for<'a> CheckBytes<RkyvValidator<'a>>,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn into_res(self) -> Result<Response, ServerFnError<CustErr>> {
|
||||
let encoded = rkyv::to_bytes::<rancor::Error>(&self)
|
||||
.map_err(|e| ServerFnError::Serialization(format!("{e:?}")))?;
|
||||
async fn into_res(self) -> Result<Response, E> {
|
||||
let encoded = rkyv::to_bytes::<rancor::Error>(&self).map_err(|e| {
|
||||
ServerFnErrorErr::Serialization(format!("{e:?}")).into_app_error()
|
||||
})?;
|
||||
let bytes = Bytes::copy_from_slice(encoded.as_ref());
|
||||
Response::try_from_bytes(Rkyv::CONTENT_TYPE, bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, T, Response> FromRes<Rkyv, Response, CustErr> for T
|
||||
impl<E, T, Response> FromRes<Rkyv, Response, E> for T
|
||||
where
|
||||
Response: ClientRes<CustErr> + Send,
|
||||
Response: ClientRes<E> + Send,
|
||||
T: Archive + for<'a> Serialize<RkyvSerializer<'a>>,
|
||||
T::Archived: Deserialize<T, RkyvDeserializer>
|
||||
+ for<'a> CheckBytes<RkyvValidator<'a>>,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn from_res(res: Response) -> Result<Self, ServerFnError<CustErr>> {
|
||||
async fn from_res(res: Response) -> Result<Self, E> {
|
||||
let data = res.try_into_bytes().await?;
|
||||
rkyv::from_bytes::<T, rancor::Error>(&data)
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
rkyv::from_bytes::<T, rancor::Error>(&data).map_err(|e| {
|
||||
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use super::{Encoding, FromReq, FromRes};
|
||||
use crate::{
|
||||
error::ServerFnError,
|
||||
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
|
||||
request::{ClientReq, Req},
|
||||
response::{ClientRes, Res},
|
||||
response::{ClientRes, TryRes},
|
||||
IntoReq, IntoRes,
|
||||
};
|
||||
use http::Method;
|
||||
@@ -15,68 +15,68 @@ impl Encoding for SerdeLite {
|
||||
const METHOD: Method = Method::POST;
|
||||
}
|
||||
|
||||
impl<CustErr, T, Request> IntoReq<SerdeLite, Request, CustErr> for T
|
||||
impl<E, T, Request> IntoReq<SerdeLite, Request, E> for T
|
||||
where
|
||||
Request: ClientReq<CustErr>,
|
||||
Request: ClientReq<E>,
|
||||
T: Serialize + Send,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
fn into_req(
|
||||
self,
|
||||
path: &str,
|
||||
accepts: &str,
|
||||
) -> Result<Request, ServerFnError<CustErr>> {
|
||||
let data = serde_json::to_string(
|
||||
&self
|
||||
.serialize()
|
||||
.map_err(|e| ServerFnError::Serialization(e.to_string()))?,
|
||||
)
|
||||
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
|
||||
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
|
||||
let data = serde_json::to_string(&self.serialize().map_err(|e| {
|
||||
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
|
||||
})?)
|
||||
.map_err(|e| {
|
||||
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
|
||||
})?;
|
||||
Request::try_new_post(path, accepts, SerdeLite::CONTENT_TYPE, data)
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, T, Request> FromReq<SerdeLite, Request, CustErr> for T
|
||||
impl<E, T, Request> FromReq<SerdeLite, Request, E> for T
|
||||
where
|
||||
Request: Req<CustErr> + Send + 'static,
|
||||
Request: Req<E> + Send + 'static,
|
||||
T: Deserialize,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn from_req(req: Request) -> Result<Self, ServerFnError<CustErr>> {
|
||||
async fn from_req(req: Request) -> Result<Self, E> {
|
||||
let string_data = req.try_into_string().await?;
|
||||
Self::deserialize(
|
||||
&serde_json::from_str(&string_data)
|
||||
.map_err(|e| ServerFnError::Args(e.to_string()))?,
|
||||
)
|
||||
.map_err(|e| ServerFnError::Args(e.to_string()))
|
||||
Self::deserialize(&serde_json::from_str(&string_data).map_err(|e| {
|
||||
ServerFnErrorErr::Args(e.to_string()).into_app_error()
|
||||
})?)
|
||||
.map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error())
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, T, Response> IntoRes<SerdeLite, Response, CustErr> for T
|
||||
impl<E, T, Response> IntoRes<SerdeLite, Response, E> for T
|
||||
where
|
||||
Response: Res<CustErr>,
|
||||
Response: TryRes<E>,
|
||||
T: Serialize + Send,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn into_res(self) -> Result<Response, ServerFnError<CustErr>> {
|
||||
let data = serde_json::to_string(
|
||||
&self
|
||||
.serialize()
|
||||
.map_err(|e| ServerFnError::Serialization(e.to_string()))?,
|
||||
)
|
||||
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
|
||||
async fn into_res(self) -> Result<Response, E> {
|
||||
let data = serde_json::to_string(&self.serialize().map_err(|e| {
|
||||
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
|
||||
})?)
|
||||
.map_err(|e| {
|
||||
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
|
||||
})?;
|
||||
Response::try_from_string(SerdeLite::CONTENT_TYPE, data)
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, T, Response> FromRes<SerdeLite, Response, CustErr> for T
|
||||
impl<E, T, Response> FromRes<SerdeLite, Response, E> for T
|
||||
where
|
||||
Response: ClientRes<CustErr> + Send,
|
||||
Response: ClientRes<E> + Send,
|
||||
T: Deserialize + Send,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn from_res(res: Response) -> Result<Self, ServerFnError<CustErr>> {
|
||||
async fn from_res(res: Response) -> Result<Self, E> {
|
||||
let data = res.try_into_string().await?;
|
||||
Self::deserialize(
|
||||
&serde_json::from_str(&data)
|
||||
.map_err(|e| ServerFnError::Args(e.to_string()))?,
|
||||
)
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
Self::deserialize(&serde_json::from_str(&data).map_err(|e| {
|
||||
ServerFnErrorErr::Args(e.to_string()).into_app_error()
|
||||
})?)
|
||||
.map_err(|e| {
|
||||
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use super::{Encoding, FromReq, FromRes, IntoReq};
|
||||
use crate::{
|
||||
error::{NoCustomError, ServerFnError},
|
||||
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
|
||||
request::{ClientReq, Req},
|
||||
response::{ClientRes, Res},
|
||||
IntoRes,
|
||||
response::{ClientRes, TryRes},
|
||||
IntoRes, ServerFnError,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use futures::{Stream, StreamExt};
|
||||
@@ -29,26 +29,22 @@ impl Encoding for Streaming {
|
||||
const METHOD: Method = Method::POST;
|
||||
}
|
||||
|
||||
impl<CustErr, T, Request> IntoReq<Streaming, Request, CustErr> for T
|
||||
impl<E, T, Request> IntoReq<Streaming, Request, E> for T
|
||||
where
|
||||
Request: ClientReq<CustErr>,
|
||||
Request: ClientReq<E>,
|
||||
T: Stream<Item = Bytes> + Send + Sync + 'static,
|
||||
{
|
||||
fn into_req(
|
||||
self,
|
||||
path: &str,
|
||||
accepts: &str,
|
||||
) -> Result<Request, ServerFnError<CustErr>> {
|
||||
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
|
||||
Request::try_new_streaming(path, accepts, Streaming::CONTENT_TYPE, self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, T, Request> FromReq<Streaming, Request, CustErr> for T
|
||||
impl<E, T, Request> FromReq<Streaming, Request, E> for T
|
||||
where
|
||||
Request: Req<CustErr> + Send + 'static,
|
||||
T: From<ByteStream> + 'static,
|
||||
Request: Req<E> + Send + 'static,
|
||||
T: From<ByteStream<E>> + 'static,
|
||||
{
|
||||
async fn from_req(req: Request) -> Result<Self, ServerFnError<CustErr>> {
|
||||
async fn from_req(req: Request) -> Result<Self, E> {
|
||||
let data = req.try_into_stream()?;
|
||||
let s = ByteStream::new(data);
|
||||
Ok(s.into())
|
||||
@@ -67,29 +63,25 @@ where
|
||||
/// end before the output will begin.
|
||||
///
|
||||
/// Streaming requests are only allowed over HTTP2 or HTTP3.
|
||||
pub struct ByteStream<CustErr = NoCustomError>(
|
||||
Pin<Box<dyn Stream<Item = Result<Bytes, ServerFnError<CustErr>>> + Send>>,
|
||||
);
|
||||
pub struct ByteStream<E>(Pin<Box<dyn Stream<Item = Result<Bytes, E>> + Send>>);
|
||||
|
||||
impl<CustErr> ByteStream<CustErr> {
|
||||
impl<E> ByteStream<E> {
|
||||
/// Consumes the wrapper, returning a stream of bytes.
|
||||
pub fn into_inner(
|
||||
self,
|
||||
) -> impl Stream<Item = Result<Bytes, ServerFnError<CustErr>>> + Send {
|
||||
pub fn into_inner(self) -> impl Stream<Item = Result<Bytes, E>> + Send {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr> Debug for ByteStream<CustErr> {
|
||||
impl<E> Debug for ByteStream<E> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("ByteStream").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl ByteStream {
|
||||
impl<E> ByteStream<E> {
|
||||
/// Creates a new `ByteStream` from the given stream.
|
||||
pub fn new<T>(
|
||||
value: impl Stream<Item = Result<T, ServerFnError>> + Send + 'static,
|
||||
value: impl Stream<Item = Result<T, E>> + Send + 'static,
|
||||
) -> Self
|
||||
where
|
||||
T: Into<Bytes>,
|
||||
@@ -98,7 +90,7 @@ impl ByteStream {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T> From<S> for ByteStream
|
||||
impl<E, S, T> From<S> for ByteStream<E>
|
||||
where
|
||||
S: Stream<Item = T> + Send + 'static,
|
||||
T: Into<Bytes>,
|
||||
@@ -108,22 +100,21 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, Response> IntoRes<Streaming, Response, CustErr>
|
||||
for ByteStream<CustErr>
|
||||
impl<E, Response> IntoRes<Streaming, Response, E> for ByteStream<E>
|
||||
where
|
||||
Response: Res<CustErr>,
|
||||
CustErr: 'static,
|
||||
Response: TryRes<E>,
|
||||
E: 'static,
|
||||
{
|
||||
async fn into_res(self) -> Result<Response, ServerFnError<CustErr>> {
|
||||
async fn into_res(self) -> Result<Response, E> {
|
||||
Response::try_from_stream(Streaming::CONTENT_TYPE, self.into_inner())
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, Response> FromRes<Streaming, Response, CustErr> for ByteStream
|
||||
impl<E, Response> FromRes<Streaming, Response, E> for ByteStream<E>
|
||||
where
|
||||
Response: ClientRes<CustErr> + Send,
|
||||
Response: ClientRes<E> + Send,
|
||||
{
|
||||
async fn from_res(res: Response) -> Result<Self, ServerFnError<CustErr>> {
|
||||
async fn from_res(res: Response) -> Result<Self, E> {
|
||||
let stream = res.try_into_stream()?;
|
||||
Ok(ByteStream(Box::pin(stream)))
|
||||
}
|
||||
@@ -160,35 +151,33 @@ impl Encoding for StreamingText {
|
||||
/// end before the output will begin.
|
||||
///
|
||||
/// Streaming requests are only allowed over HTTP2 or HTTP3.
|
||||
pub struct TextStream<CustErr = NoCustomError>(
|
||||
Pin<Box<dyn Stream<Item = Result<String, ServerFnError<CustErr>>> + Send>>,
|
||||
pub struct TextStream<E = ServerFnError>(
|
||||
Pin<Box<dyn Stream<Item = Result<String, E>> + Send>>,
|
||||
);
|
||||
|
||||
impl<CustErr> Debug for TextStream<CustErr> {
|
||||
impl<E> Debug for TextStream<E> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("TextStream").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl TextStream {
|
||||
impl<E> TextStream<E> {
|
||||
/// Creates a new `ByteStream` from the given stream.
|
||||
pub fn new(
|
||||
value: impl Stream<Item = Result<String, ServerFnError>> + Send + 'static,
|
||||
value: impl Stream<Item = Result<String, E>> + Send + 'static,
|
||||
) -> Self {
|
||||
Self(Box::pin(value.map(|value| value)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr> TextStream<CustErr> {
|
||||
impl<E> TextStream<E> {
|
||||
/// Consumes the wrapper, returning a stream of text.
|
||||
pub fn into_inner(
|
||||
self,
|
||||
) -> impl Stream<Item = Result<String, ServerFnError<CustErr>>> + Send {
|
||||
pub fn into_inner(self) -> impl Stream<Item = Result<String, E>> + Send {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T> From<S> for TextStream
|
||||
impl<E, S, T> From<S> for TextStream<E>
|
||||
where
|
||||
S: Stream<Item = T> + Send + 'static,
|
||||
T: Into<String>,
|
||||
@@ -198,16 +187,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, T, Request> IntoReq<StreamingText, Request, CustErr> for T
|
||||
impl<E, T, Request> IntoReq<StreamingText, Request, E> for T
|
||||
where
|
||||
Request: ClientReq<CustErr>,
|
||||
T: Into<TextStream>,
|
||||
Request: ClientReq<E>,
|
||||
T: Into<TextStream<E>>,
|
||||
E: 'static,
|
||||
{
|
||||
fn into_req(
|
||||
self,
|
||||
path: &str,
|
||||
accepts: &str,
|
||||
) -> Result<Request, ServerFnError<CustErr>> {
|
||||
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
|
||||
let data = self.into();
|
||||
Request::try_new_streaming(
|
||||
path,
|
||||
@@ -218,30 +204,32 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, T, Request> FromReq<StreamingText, Request, CustErr> for T
|
||||
impl<E, T, Request> FromReq<StreamingText, Request, E> for T
|
||||
where
|
||||
Request: Req<CustErr> + Send + 'static,
|
||||
T: From<TextStream> + 'static,
|
||||
Request: Req<E> + Send + 'static,
|
||||
T: From<TextStream<E>> + 'static,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn from_req(req: Request) -> Result<Self, ServerFnError<CustErr>> {
|
||||
async fn from_req(req: Request) -> Result<Self, E> {
|
||||
let data = req.try_into_stream()?;
|
||||
let s = TextStream::new(data.map(|chunk| {
|
||||
chunk.and_then(|bytes| {
|
||||
String::from_utf8(bytes.to_vec())
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
String::from_utf8(bytes.to_vec()).map_err(|e| {
|
||||
ServerFnErrorErr::Deserialization(e.to_string())
|
||||
.into_app_error()
|
||||
})
|
||||
})
|
||||
}));
|
||||
Ok(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, Response> IntoRes<StreamingText, Response, CustErr>
|
||||
for TextStream<CustErr>
|
||||
impl<E, Response> IntoRes<StreamingText, Response, E> for TextStream<E>
|
||||
where
|
||||
Response: Res<CustErr>,
|
||||
CustErr: 'static,
|
||||
Response: TryRes<E>,
|
||||
E: 'static,
|
||||
{
|
||||
async fn into_res(self) -> Result<Response, ServerFnError<CustErr>> {
|
||||
async fn into_res(self) -> Result<Response, E> {
|
||||
Response::try_from_stream(
|
||||
Streaming::CONTENT_TYPE,
|
||||
self.into_inner().map(|stream| stream.map(Into::into)),
|
||||
@@ -249,16 +237,19 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, Response> FromRes<StreamingText, Response, CustErr> for TextStream
|
||||
impl<E, Response> FromRes<StreamingText, Response, E> for TextStream<E>
|
||||
where
|
||||
Response: ClientRes<CustErr> + Send,
|
||||
Response: ClientRes<E> + Send,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn from_res(res: Response) -> Result<Self, ServerFnError<CustErr>> {
|
||||
async fn from_res(res: Response) -> Result<Self, E> {
|
||||
let stream = res.try_into_stream()?;
|
||||
Ok(TextStream(Box::pin(stream.map(|chunk| {
|
||||
chunk.and_then(|bytes| {
|
||||
String::from_utf8(bytes.into())
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
String::from_utf8(bytes.into()).map_err(|e| {
|
||||
ServerFnErrorErr::Deserialization(e.to_string())
|
||||
.into_app_error()
|
||||
})
|
||||
})
|
||||
}))))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::{Encoding, FromReq, IntoReq};
|
||||
use crate::{
|
||||
error::ServerFnError,
|
||||
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
|
||||
request::{ClientReq, Req},
|
||||
};
|
||||
use http::Method;
|
||||
@@ -17,32 +17,33 @@ impl Encoding for GetUrl {
|
||||
const METHOD: Method = Method::GET;
|
||||
}
|
||||
|
||||
impl<CustErr, T, Request> IntoReq<GetUrl, Request, CustErr> for T
|
||||
impl<E, T, Request> IntoReq<GetUrl, Request, E> for T
|
||||
where
|
||||
Request: ClientReq<CustErr>,
|
||||
Request: ClientReq<E>,
|
||||
T: Serialize + Send,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
fn into_req(
|
||||
self,
|
||||
path: &str,
|
||||
accepts: &str,
|
||||
) -> Result<Request, ServerFnError<CustErr>> {
|
||||
let data = serde_qs::to_string(&self)
|
||||
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
|
||||
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
|
||||
let data = serde_qs::to_string(&self).map_err(|e| {
|
||||
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
|
||||
})?;
|
||||
Request::try_new_get(path, accepts, GetUrl::CONTENT_TYPE, &data)
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, T, Request> FromReq<GetUrl, Request, CustErr> for T
|
||||
impl<E, T, Request> FromReq<GetUrl, Request, E> for T
|
||||
where
|
||||
Request: Req<CustErr> + Send + 'static,
|
||||
Request: Req<E> + Send + 'static,
|
||||
T: DeserializeOwned,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn from_req(req: Request) -> Result<Self, ServerFnError<CustErr>> {
|
||||
async fn from_req(req: Request) -> Result<Self, E> {
|
||||
let string_data = req.as_query().unwrap_or_default();
|
||||
let args = serde_qs::Config::new(5, false)
|
||||
.deserialize_str::<Self>(string_data)
|
||||
.map_err(|e| ServerFnError::Args(e.to_string()))?;
|
||||
.map_err(|e| {
|
||||
ServerFnErrorErr::Args(e.to_string()).into_app_error()
|
||||
})?;
|
||||
Ok(args)
|
||||
}
|
||||
}
|
||||
@@ -52,32 +53,33 @@ impl Encoding for PostUrl {
|
||||
const METHOD: Method = Method::POST;
|
||||
}
|
||||
|
||||
impl<CustErr, T, Request> IntoReq<PostUrl, Request, CustErr> for T
|
||||
impl<E, T, Request> IntoReq<PostUrl, Request, E> for T
|
||||
where
|
||||
Request: ClientReq<CustErr>,
|
||||
Request: ClientReq<E>,
|
||||
T: Serialize + Send,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
fn into_req(
|
||||
self,
|
||||
path: &str,
|
||||
accepts: &str,
|
||||
) -> Result<Request, ServerFnError<CustErr>> {
|
||||
let qs = serde_qs::to_string(&self)
|
||||
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
|
||||
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
|
||||
let qs = serde_qs::to_string(&self).map_err(|e| {
|
||||
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
|
||||
})?;
|
||||
Request::try_new_post(path, accepts, PostUrl::CONTENT_TYPE, qs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr, T, Request> FromReq<PostUrl, Request, CustErr> for T
|
||||
impl<E, T, Request> FromReq<PostUrl, Request, E> for T
|
||||
where
|
||||
Request: Req<CustErr> + Send + 'static,
|
||||
Request: Req<E> + Send + 'static,
|
||||
T: DeserializeOwned,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn from_req(req: Request) -> Result<Self, ServerFnError<CustErr>> {
|
||||
async fn from_req(req: Request) -> Result<Self, E> {
|
||||
let string_data = req.try_into_string().await?;
|
||||
let args = serde_qs::Config::new(5, false)
|
||||
.deserialize_str::<Self>(&string_data)
|
||||
.map_err(|e| ServerFnError::Args(e.to_string()))?;
|
||||
.map_err(|e| {
|
||||
ServerFnErrorErr::Args(e.to_string()).into_app_error()
|
||||
})?;
|
||||
Ok(args)
|
||||
}
|
||||
}
|
||||
@@ -86,18 +88,18 @@ where
|
||||
impl<T, Request, Response> Codec<Request, Response, GetUrlJson> for T
|
||||
where
|
||||
T: DeserializeOwned + Serialize + Send,
|
||||
Request: Req<CustErr> + Send,
|
||||
Response: Res<CustErr> + Send,
|
||||
Request: Req<E> + Send,
|
||||
Response: Res<E> + Send,
|
||||
{
|
||||
async fn from_req(req: Request) -> Result<Self, ServerFnError<CustErr>> {
|
||||
async fn from_req(req: Request) -> Result<Self, ServerFnError<E>> {
|
||||
let string_data = req.try_into_string()?;
|
||||
|
||||
let args = serde_json::from_str::<Self>(&string_data)
|
||||
.map_err(|e| ServerFnError::Args(e.to_string()))?;
|
||||
.map_err(|e| ServerFnErrorErr::Args(e.to_string()).into())?;
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
async fn into_req(self) -> Result<Request, ServerFnError<CustErr>> {
|
||||
async fn into_req(self) -> Result<Request, ServerFnError<E>> {
|
||||
/* let qs = serde_qs::to_string(&self)?;
|
||||
let req = http::Request::builder()
|
||||
.method("GET")
|
||||
@@ -110,7 +112,7 @@ where
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn from_res(res: Response) -> Result<Self, ServerFnError<CustErr>> {
|
||||
async fn from_res(res: Response) -> Result<Self, ServerFnError<E>> {
|
||||
todo!()
|
||||
/* let (_parts, body) = res.into_parts();
|
||||
|
||||
@@ -118,7 +120,7 @@ where
|
||||
.collect()
|
||||
.await
|
||||
.map(|c| c.to_bytes())
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))?;
|
||||
.map_err(|e| ServerFnErrorErr::Deserialization(e.to_string()).into())?;
|
||||
let string_data = String::from_utf8(body_bytes.to_vec())?;
|
||||
serde_json::from_str(&string_data)
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string())) */
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
#![allow(deprecated)]
|
||||
|
||||
use base64::{engine::general_purpose::URL_SAFE, Engine as _};
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use std::{
|
||||
fmt,
|
||||
fmt::{Display, Write},
|
||||
fmt::{self, Display, Write},
|
||||
str::FromStr,
|
||||
};
|
||||
use thiserror::Error;
|
||||
@@ -13,7 +15,7 @@ pub const SERVER_FN_ERROR_HEADER: &str = "serverfnerror";
|
||||
|
||||
impl From<ServerFnError> for Error {
|
||||
fn from(e: ServerFnError) -> Self {
|
||||
Error::from(ServerFnErrorErr::from(e))
|
||||
Error::from(ServerFnErrorWrapper(e))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +37,11 @@ impl From<ServerFnError> for Error {
|
||||
feature = "rkyv",
|
||||
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
|
||||
)]
|
||||
#[deprecated(
|
||||
since = "0.8.0",
|
||||
note = "Now server_fn can return any error type other than ServerFnError, \
|
||||
so the WrappedServerError variant will be removed in 0.9.0"
|
||||
)]
|
||||
pub struct NoCustomError;
|
||||
|
||||
// Implement `Display` for `NoCustomError`
|
||||
@@ -55,11 +62,21 @@ impl FromStr for NoCustomError {
|
||||
/// Wraps some error type, which may implement any of [`Error`](trait@std::error::Error), [`Clone`], or
|
||||
/// [`Display`].
|
||||
#[derive(Debug)]
|
||||
#[deprecated(
|
||||
since = "0.8.0",
|
||||
note = "Now server_fn can return any error type other than ServerFnError, \
|
||||
so the WrappedServerError variant will be removed in 0.9.0"
|
||||
)]
|
||||
pub struct WrapError<T>(pub T);
|
||||
|
||||
/// A helper macro to convert a variety of different types into `ServerFnError`.
|
||||
/// This should mostly be used if you are implementing `From<ServerFnError>` for `YourError`.
|
||||
#[macro_export]
|
||||
#[deprecated(
|
||||
since = "0.8.0",
|
||||
note = "Now server_fn can return any error type other than ServerFnError, \
|
||||
so the WrappedServerError variant will be removed in 0.9.0"
|
||||
)]
|
||||
macro_rules! server_fn_error {
|
||||
() => {{
|
||||
use $crate::{ViaError, WrapError};
|
||||
@@ -75,6 +92,12 @@ macro_rules! server_fn_error {
|
||||
|
||||
/// This trait serves as the conversion method between a variety of types
|
||||
/// and [`ServerFnError`].
|
||||
#[deprecated(
|
||||
since = "0.8.0",
|
||||
note = "Now server_fn can return any error type other than ServerFnError, \
|
||||
so users should place their custom error type instead of \
|
||||
ServerFnError"
|
||||
)]
|
||||
pub trait ViaError<E> {
|
||||
/// Converts something into an error.
|
||||
fn to_server_error(&self) -> ServerFnError<E>;
|
||||
@@ -90,6 +113,7 @@ impl<E: ServerFnErrorKind + std::error::Error + Clone> ViaError<E>
|
||||
}
|
||||
|
||||
// A type tag for ServerFnError so we can special case it
|
||||
#[deprecated]
|
||||
pub(crate) trait ServerFnErrorKind {}
|
||||
|
||||
impl ServerFnErrorKind for ServerFnError {}
|
||||
@@ -131,7 +155,8 @@ impl<E> ViaError<E> for WrapError<E> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Type for errors that can occur when using server functions.
|
||||
/// A type that can be used as the return type of the server function for easy error conversion with `?` operator.
|
||||
/// This type can be replaced with any other error type that implements `FromServerFnError`.
|
||||
///
|
||||
/// Unlike [`ServerFnErrorErr`], this does not implement [`Error`](trait@std::error::Error).
|
||||
/// This means that other error types can easily be converted into it using the
|
||||
@@ -142,6 +167,12 @@ impl<E> ViaError<E> for WrapError<E> {
|
||||
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
|
||||
)]
|
||||
pub enum ServerFnError<E = NoCustomError> {
|
||||
#[deprecated(
|
||||
since = "0.8.0",
|
||||
note = "Now server_fn can return any error type other than \
|
||||
ServerFnError, so users should place their custom error type \
|
||||
instead of ServerFnError"
|
||||
)]
|
||||
/// A user-defined custom error type, which defaults to [`NoCustomError`].
|
||||
WrappedServerError(E),
|
||||
/// Error while trying to register the server function (only occurs in case of poisoned RwLock).
|
||||
@@ -152,6 +183,8 @@ pub enum ServerFnError<E = NoCustomError> {
|
||||
Response(String),
|
||||
/// Occurs when there is an error while actually running the function on the server.
|
||||
ServerError(String),
|
||||
/// Occurs when there is an error while actually running the middleware on the server.
|
||||
MiddlewareError(String),
|
||||
/// Occurs on the client if there is an error deserializing the server's response.
|
||||
Deserialization(String),
|
||||
/// Occurs on the client if there is an error serializing the server function arguments.
|
||||
@@ -198,6 +231,8 @@ where
|
||||
),
|
||||
ServerFnError::ServerError(s) =>
|
||||
format!("error running server function: {s}"),
|
||||
ServerFnError::MiddlewareError(s) =>
|
||||
format!("error running middleware: {s}"),
|
||||
ServerFnError::Deserialization(s) =>
|
||||
format!("error deserializing server function results: {s}"),
|
||||
ServerFnError::Serialization(s) =>
|
||||
@@ -214,30 +249,45 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// A serializable custom server function error type.
|
||||
///
|
||||
/// This is implemented for all types that implement [`FromStr`] + [`Display`].
|
||||
///
|
||||
/// This means you do not necessarily need the overhead of `serde` for a custom error type.
|
||||
/// Instead, you can use something like `strum` to derive `FromStr` and `Display` for your
|
||||
/// custom error type.
|
||||
///
|
||||
/// This is implemented for the default [`ServerFnError`], which uses [`NoCustomError`].
|
||||
pub trait ServerFnErrorSerde: Sized {
|
||||
/// Converts the custom error type to a [`String`].
|
||||
fn ser(&self) -> Result<String, std::fmt::Error>;
|
||||
|
||||
/// Deserializes the custom error type from a [`String`].
|
||||
fn de(data: &str) -> Self;
|
||||
}
|
||||
|
||||
impl<CustErr> ServerFnErrorSerde for ServerFnError<CustErr>
|
||||
impl<CustErr> FromServerFnError for ServerFnError<CustErr>
|
||||
where
|
||||
CustErr: FromStr + Display,
|
||||
CustErr: std::fmt::Debug
|
||||
+ Display
|
||||
+ Serialize
|
||||
+ DeserializeOwned
|
||||
+ 'static
|
||||
+ FromStr
|
||||
+ Display,
|
||||
{
|
||||
fn ser(&self) -> Result<String, std::fmt::Error> {
|
||||
fn from_server_fn_error(value: ServerFnErrorErr) -> Self {
|
||||
match value {
|
||||
ServerFnErrorErr::Registration(value) => {
|
||||
ServerFnError::Registration(value)
|
||||
}
|
||||
ServerFnErrorErr::Request(value) => ServerFnError::Request(value),
|
||||
ServerFnErrorErr::ServerError(value) => {
|
||||
ServerFnError::ServerError(value)
|
||||
}
|
||||
ServerFnErrorErr::MiddlewareError(value) => {
|
||||
ServerFnError::MiddlewareError(value)
|
||||
}
|
||||
ServerFnErrorErr::Deserialization(value) => {
|
||||
ServerFnError::Deserialization(value)
|
||||
}
|
||||
ServerFnErrorErr::Serialization(value) => {
|
||||
ServerFnError::Serialization(value)
|
||||
}
|
||||
ServerFnErrorErr::Args(value) => ServerFnError::Args(value),
|
||||
ServerFnErrorErr::MissingArg(value) => {
|
||||
ServerFnError::MissingArg(value)
|
||||
}
|
||||
ServerFnErrorErr::Response(value) => ServerFnError::Response(value),
|
||||
}
|
||||
}
|
||||
|
||||
fn ser(&self) -> String {
|
||||
let mut buf = String::new();
|
||||
match self {
|
||||
let result = match self {
|
||||
ServerFnError::WrappedServerError(e) => {
|
||||
write!(&mut buf, "WrappedServerFn|{e}")
|
||||
}
|
||||
@@ -249,6 +299,9 @@ where
|
||||
ServerFnError::ServerError(e) => {
|
||||
write!(&mut buf, "ServerError|{e}")
|
||||
}
|
||||
ServerFnError::MiddlewareError(e) => {
|
||||
write!(&mut buf, "MiddlewareError|{e}")
|
||||
}
|
||||
ServerFnError::Deserialization(e) => {
|
||||
write!(&mut buf, "Deserialization|{e}")
|
||||
}
|
||||
@@ -259,8 +312,11 @@ where
|
||||
ServerFnError::MissingArg(e) => {
|
||||
write!(&mut buf, "MissingArg|{e}")
|
||||
}
|
||||
}?;
|
||||
Ok(buf)
|
||||
};
|
||||
match result {
|
||||
Ok(()) => buf,
|
||||
Err(_) => "Serialization|".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn de(data: &str) -> Self {
|
||||
@@ -311,20 +367,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Type for errors that can occur when using server functions.
|
||||
///
|
||||
/// Unlike [`ServerFnError`], this implements [`std::error::Error`]. This means
|
||||
/// it can be used in situations in which the `Error` trait is required, but it’s
|
||||
/// not possible to create a blanket implementation that converts other errors into
|
||||
/// this type.
|
||||
///
|
||||
/// [`ServerFnError`] and [`ServerFnErrorErr`] mutually implement [`From`], so
|
||||
/// it is easy to convert between the two types.
|
||||
#[derive(Error, Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ServerFnErrorErr<E = NoCustomError> {
|
||||
/// A user-defined custom error type, which defaults to [`NoCustomError`].
|
||||
#[error("internal error: {0}")]
|
||||
WrappedServerError(E),
|
||||
/// Type for errors that can occur when using server functions. If you need to return a custom error type from a server function, implement `FromServerFnError` for your custom error type.
|
||||
#[derive(Error, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[cfg_attr(
|
||||
feature = "rkyv",
|
||||
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
|
||||
)]
|
||||
pub enum ServerFnErrorErr {
|
||||
/// Error while trying to register the server function (only occurs in case of poisoned RwLock).
|
||||
#[error("error while trying to register the server function: {0}")]
|
||||
Registration(String),
|
||||
@@ -334,6 +383,9 @@ pub enum ServerFnErrorErr<E = NoCustomError> {
|
||||
/// Occurs when there is an error while actually running the function on the server.
|
||||
#[error("error running server function: {0}")]
|
||||
ServerError(String),
|
||||
/// Occurs when there is an error while actually running the middleware on the server.
|
||||
#[error("error running middleware: {0}")]
|
||||
MiddlewareError(String),
|
||||
/// Occurs on the client if there is an error deserializing the server's response.
|
||||
#[error("error deserializing server function results: {0}")]
|
||||
Deserialization(String),
|
||||
@@ -351,34 +403,6 @@ pub enum ServerFnErrorErr<E = NoCustomError> {
|
||||
Response(String),
|
||||
}
|
||||
|
||||
impl<CustErr> From<ServerFnError<CustErr>> for ServerFnErrorErr<CustErr> {
|
||||
fn from(value: ServerFnError<CustErr>) -> Self {
|
||||
match value {
|
||||
ServerFnError::Registration(value) => {
|
||||
ServerFnErrorErr::Registration(value)
|
||||
}
|
||||
ServerFnError::Request(value) => ServerFnErrorErr::Request(value),
|
||||
ServerFnError::ServerError(value) => {
|
||||
ServerFnErrorErr::ServerError(value)
|
||||
}
|
||||
ServerFnError::Deserialization(value) => {
|
||||
ServerFnErrorErr::Deserialization(value)
|
||||
}
|
||||
ServerFnError::Serialization(value) => {
|
||||
ServerFnErrorErr::Serialization(value)
|
||||
}
|
||||
ServerFnError::Args(value) => ServerFnErrorErr::Args(value),
|
||||
ServerFnError::MissingArg(value) => {
|
||||
ServerFnErrorErr::MissingArg(value)
|
||||
}
|
||||
ServerFnError::WrappedServerError(value) => {
|
||||
ServerFnErrorErr::WrappedServerError(value)
|
||||
}
|
||||
ServerFnError::Response(value) => ServerFnErrorErr::Response(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Associates a particular server function error with the server function
|
||||
/// found at a particular path.
|
||||
///
|
||||
@@ -386,15 +410,15 @@ impl<CustErr> From<ServerFnError<CustErr>> for ServerFnErrorErr<CustErr> {
|
||||
/// without JavaScript/WASM supported, by encoding it in the URL as a query string.
|
||||
/// This is useful for progressive enhancement.
|
||||
#[derive(Debug)]
|
||||
pub struct ServerFnUrlError<CustErr> {
|
||||
pub struct ServerFnUrlError<E> {
|
||||
path: String,
|
||||
error: ServerFnError<CustErr>,
|
||||
error: E,
|
||||
}
|
||||
|
||||
impl<CustErr> ServerFnUrlError<CustErr> {
|
||||
impl<E: FromServerFnError> ServerFnUrlError<E> {
|
||||
/// Creates a new structure associating the server function at some path
|
||||
/// with a particular error.
|
||||
pub fn new(path: impl Display, error: ServerFnError<CustErr>) -> Self {
|
||||
pub fn new(path: impl Display, error: E) -> Self {
|
||||
Self {
|
||||
path: path.to_string(),
|
||||
error,
|
||||
@@ -402,7 +426,7 @@ impl<CustErr> ServerFnUrlError<CustErr> {
|
||||
}
|
||||
|
||||
/// The error itself.
|
||||
pub fn error(&self) -> &ServerFnError<CustErr> {
|
||||
pub fn error(&self) -> &E {
|
||||
&self.error
|
||||
}
|
||||
|
||||
@@ -412,17 +436,11 @@ impl<CustErr> ServerFnUrlError<CustErr> {
|
||||
}
|
||||
|
||||
/// Adds an encoded form of this server function error to the given base URL.
|
||||
pub fn to_url(&self, base: &str) -> Result<Url, url::ParseError>
|
||||
where
|
||||
CustErr: FromStr + Display,
|
||||
{
|
||||
pub fn to_url(&self, base: &str) -> Result<Url, url::ParseError> {
|
||||
let mut url = Url::parse(base)?;
|
||||
url.query_pairs_mut()
|
||||
.append_pair("__path", &self.path)
|
||||
.append_pair(
|
||||
"__err",
|
||||
&ServerFnErrorSerde::ser(&self.error).unwrap_or_default(),
|
||||
);
|
||||
.append_pair("__err", &URL_SAFE.encode(self.error.ser()));
|
||||
Ok(url)
|
||||
}
|
||||
|
||||
@@ -448,16 +466,102 @@ impl<CustErr> ServerFnUrlError<CustErr> {
|
||||
*path = url.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
/// Decodes an error from a URL.
|
||||
pub fn decode_err(err: &str) -> E {
|
||||
let decoded = match URL_SAFE.decode(err) {
|
||||
Ok(decoded) => decoded,
|
||||
Err(err) => {
|
||||
return ServerFnErrorErr::Deserialization(err.to_string())
|
||||
.into_app_error();
|
||||
}
|
||||
};
|
||||
let s = match String::from_utf8(decoded) {
|
||||
Ok(s) => s,
|
||||
Err(err) => {
|
||||
return ServerFnErrorErr::Deserialization(err.to_string())
|
||||
.into_app_error();
|
||||
}
|
||||
};
|
||||
E::de(&s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr> From<ServerFnUrlError<CustErr>> for ServerFnError<CustErr> {
|
||||
fn from(error: ServerFnUrlError<CustErr>) -> Self {
|
||||
impl<E> From<ServerFnUrlError<E>> for ServerFnError<E> {
|
||||
fn from(error: ServerFnUrlError<E>) -> Self {
|
||||
error.error.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> From<ServerFnUrlError<ServerFnError<E>>> for ServerFnError<E> {
|
||||
fn from(error: ServerFnUrlError<ServerFnError<E>>) -> Self {
|
||||
error.error
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr> From<ServerFnUrlError<CustErr>> for ServerFnErrorErr<CustErr> {
|
||||
fn from(error: ServerFnUrlError<CustErr>) -> Self {
|
||||
error.error.into()
|
||||
#[derive(Debug)]
|
||||
#[doc(hidden)]
|
||||
/// Only used instantly only when a framework needs E: Error.
|
||||
pub struct ServerFnErrorWrapper<E: FromServerFnError>(pub E);
|
||||
|
||||
impl<E: FromServerFnError> Display for ServerFnErrorWrapper<E> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0.ser())
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: FromServerFnError> std::error::Error for ServerFnErrorWrapper<E> {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for types that can be returned from a server function.
|
||||
pub trait FromServerFnError:
|
||||
std::fmt::Debug + Serialize + DeserializeOwned + 'static
|
||||
{
|
||||
/// Converts a [`ServerFnErrorErr`] into the application-specific custom error type.
|
||||
fn from_server_fn_error(value: ServerFnErrorErr) -> Self;
|
||||
|
||||
/// Converts the custom error type to a [`String`]. Defaults to serializing to JSON.
|
||||
fn ser(&self) -> String {
|
||||
serde_json::to_string(self).unwrap_or_else(|e| {
|
||||
serde_json::to_string(&Self::from_server_fn_error(
|
||||
ServerFnErrorErr::Serialization(e.to_string()),
|
||||
))
|
||||
.expect(
|
||||
"error serializing should success at least with the \
|
||||
Serialization error",
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Deserializes the custom error type from a [`&str`]. Defaults to deserializing from JSON.
|
||||
fn de(data: &str) -> Self {
|
||||
serde_json::from_str(data).unwrap_or_else(|e| {
|
||||
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper trait for converting a [`ServerFnErrorErr`] into an application-specific custom error type that implements [`FromServerFnError`].
|
||||
pub trait IntoAppError<E> {
|
||||
/// Converts a [`ServerFnErrorErr`] into the application-specific custom error type.
|
||||
fn into_app_error(self) -> E;
|
||||
}
|
||||
|
||||
impl<E> IntoAppError<E> for ServerFnErrorErr
|
||||
where
|
||||
E: FromServerFnError,
|
||||
{
|
||||
fn into_app_error(self) -> E {
|
||||
E::from_server_fn_error(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_from_server_fn_error_impl() {
|
||||
fn assert_impl<T: FromServerFnError>() {}
|
||||
|
||||
assert_impl::<ServerFnError>();
|
||||
}
|
||||
|
||||
@@ -130,17 +130,19 @@ use client::Client;
|
||||
use codec::{Encoding, FromReq, FromRes, IntoReq, IntoRes};
|
||||
#[doc(hidden)]
|
||||
pub use const_format;
|
||||
#[doc(hidden)]
|
||||
pub use const_str;
|
||||
use dashmap::DashMap;
|
||||
pub use error::ServerFnError;
|
||||
use error::ServerFnErrorSerde;
|
||||
#[cfg(feature = "form-redirects")]
|
||||
use error::ServerFnUrlError;
|
||||
use error::{FromServerFnError, ServerFnErrorErr};
|
||||
use http::Method;
|
||||
use middleware::{Layer, Service};
|
||||
use middleware::{BoxedService, Layer, Service};
|
||||
use once_cell::sync::Lazy;
|
||||
use redirect::RedirectHook;
|
||||
use request::Req;
|
||||
use response::{ClientRes, Res};
|
||||
use response::{ClientRes, Res, TryRes};
|
||||
#[cfg(feature = "rkyv")]
|
||||
pub use rkyv;
|
||||
#[doc(hidden)]
|
||||
@@ -148,7 +150,7 @@ pub use serde;
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "serde-lite")]
|
||||
pub use serde_lite;
|
||||
use std::{fmt::Display, future::Future, pin::Pin, str::FromStr, sync::Arc};
|
||||
use std::{future::Future, pin::Pin, sync::Arc};
|
||||
#[doc(hidden)]
|
||||
pub use xxhash_rust;
|
||||
|
||||
@@ -203,7 +205,7 @@ where
|
||||
type ServerRequest: Req<Self::Error> + Send;
|
||||
|
||||
/// The type of the HTTP response returned by the server function on the server side.
|
||||
type ServerResponse: Res<Self::Error> + Send;
|
||||
type ServerResponse: Res + TryRes<Self::Error> + Send;
|
||||
|
||||
/// The return type of the server function.
|
||||
///
|
||||
@@ -222,9 +224,8 @@ where
|
||||
/// The [`Encoding`] used in the response for the result of the server function.
|
||||
type OutputEncoding: Encoding;
|
||||
|
||||
/// The type of the custom error on [`ServerFnError`], if any. (If there is no
|
||||
/// custom error type, this can be `NoCustomError` by default.)
|
||||
type Error: FromStr + Display;
|
||||
/// The type of the error on the server function. Typically [`ServerFnError`], but allowed to be any type that implements [`FromServerFnError`].
|
||||
type Error: FromServerFnError;
|
||||
|
||||
/// Returns [`Self::PATH`].
|
||||
fn url() -> &'static str {
|
||||
@@ -240,7 +241,7 @@ where
|
||||
/// The body of the server function. This will only run on the server.
|
||||
fn run_body(
|
||||
self,
|
||||
) -> impl Future<Output = Result<Self::Output, ServerFnError<Self::Error>>> + Send;
|
||||
) -> impl Future<Output = Result<Self::Output, Self::Error>> + Send;
|
||||
|
||||
#[doc(hidden)]
|
||||
fn run_on_server(
|
||||
@@ -265,7 +266,10 @@ where
|
||||
.map(|res| (res, None))
|
||||
.unwrap_or_else(|e| {
|
||||
(
|
||||
Self::ServerResponse::error_response(Self::PATH, &e),
|
||||
Self::ServerResponse::error_response(
|
||||
Self::PATH,
|
||||
e.ser(),
|
||||
),
|
||||
Some(e),
|
||||
)
|
||||
});
|
||||
@@ -298,8 +302,7 @@ where
|
||||
#[doc(hidden)]
|
||||
fn run_on_client(
|
||||
self,
|
||||
) -> impl Future<Output = Result<Self::Output, ServerFnError<Self::Error>>> + Send
|
||||
{
|
||||
) -> impl Future<Output = Result<Self::Output, Self::Error>> + Send {
|
||||
async move {
|
||||
// create and send request on client
|
||||
let req =
|
||||
@@ -313,8 +316,7 @@ where
|
||||
fn run_on_client_with_req(
|
||||
req: <Self::Client as Client<Self::Error>>::Request,
|
||||
redirect_hook: Option<&RedirectHook>,
|
||||
) -> impl Future<Output = Result<Self::Output, ServerFnError<Self::Error>>> + Send
|
||||
{
|
||||
) -> impl Future<Output = Result<Self::Output, Self::Error>> + Send {
|
||||
async move {
|
||||
let res = Self::Client::send(req).await?;
|
||||
|
||||
@@ -325,7 +327,7 @@ where
|
||||
// if it returns an error status, deserialize the error using FromStr
|
||||
let res = if (400..=599).contains(&status) {
|
||||
let text = res.try_into_string().await?;
|
||||
Err(ServerFnError::<Self::Error>::de(&text))
|
||||
Err(Self::Error::de(&text))
|
||||
} else {
|
||||
// otherwise, deserialize the body as is
|
||||
Ok(Self::Output::from_res(res).await)
|
||||
@@ -345,9 +347,8 @@ where
|
||||
#[doc(hidden)]
|
||||
fn execute_on_server(
|
||||
req: Self::ServerRequest,
|
||||
) -> impl Future<
|
||||
Output = Result<Self::ServerResponse, ServerFnError<Self::Error>>,
|
||||
> + Send {
|
||||
) -> impl Future<Output = Result<Self::ServerResponse, Self::Error>> + Send
|
||||
{
|
||||
async {
|
||||
let this = Self::from_req(req).await?;
|
||||
let output = this.run_body().await?;
|
||||
@@ -387,21 +388,20 @@ pub struct ServerFnTraitObj<Req, Res> {
|
||||
method: Method,
|
||||
handler: fn(Req) -> Pin<Box<dyn Future<Output = Res> + Send>>,
|
||||
middleware: fn() -> MiddlewareSet<Req, Res>,
|
||||
ser: fn(ServerFnErrorErr) -> String,
|
||||
}
|
||||
|
||||
impl<Req, Res> ServerFnTraitObj<Req, Res> {
|
||||
/// Converts the relevant parts of a server function into a trait object.
|
||||
pub const fn new(
|
||||
path: &'static str,
|
||||
method: Method,
|
||||
pub const fn new<S: ServerFn<ServerRequest = Req, ServerResponse = Res>>(
|
||||
handler: fn(Req) -> Pin<Box<dyn Future<Output = Res> + Send>>,
|
||||
middleware: fn() -> MiddlewareSet<Req, Res>,
|
||||
) -> Self {
|
||||
Self {
|
||||
path,
|
||||
method,
|
||||
path: S::PATH,
|
||||
method: S::InputEncoding::METHOD,
|
||||
handler,
|
||||
middleware,
|
||||
middleware: S::middlewares,
|
||||
ser: |e| S::Error::from_server_fn_error(e).ser(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,6 +424,16 @@ impl<Req, Res> ServerFnTraitObj<Req, Res> {
|
||||
pub fn middleware(&self) -> MiddlewareSet<Req, Res> {
|
||||
(self.middleware)()
|
||||
}
|
||||
|
||||
/// Converts the server function into a boxed service.
|
||||
pub fn boxed(self) -> BoxedService<Req, Res>
|
||||
where
|
||||
Self: Service<Req, Res>,
|
||||
Req: 'static,
|
||||
Res: 'static,
|
||||
{
|
||||
BoxedService::new(self.ser, self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Req, Res> Service<Req, Res> for ServerFnTraitObj<Req, Res>
|
||||
@@ -431,7 +441,11 @@ where
|
||||
Req: Send + 'static,
|
||||
Res: 'static,
|
||||
{
|
||||
fn run(&mut self, req: Req) -> Pin<Box<dyn Future<Output = Res> + Send>> {
|
||||
fn run(
|
||||
&mut self,
|
||||
req: Req,
|
||||
_ser: fn(ServerFnErrorErr) -> String,
|
||||
) -> Pin<Box<dyn Future<Output = Res> + Send>> {
|
||||
let handler = self.handler;
|
||||
Box::pin(async move { handler(req).await })
|
||||
}
|
||||
@@ -444,6 +458,7 @@ impl<Req, Res> Clone for ServerFnTraitObj<Req, Res> {
|
||||
method: self.method.clone(),
|
||||
handler: self.handler,
|
||||
middleware: self.middleware,
|
||||
ser: self.ser,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -467,8 +482,8 @@ impl<Req: 'static, Res: 'static> inventory::Collect
|
||||
#[cfg(feature = "axum-no-default")]
|
||||
pub mod axum {
|
||||
use crate::{
|
||||
middleware::{BoxedService, Service},
|
||||
Encoding, LazyServerFnMap, ServerFn, ServerFnTraitObj,
|
||||
middleware::BoxedService, Encoding, LazyServerFnMap, ServerFn,
|
||||
ServerFnTraitObj,
|
||||
};
|
||||
use axum::body::Body;
|
||||
use http::{Method, Request, Response, StatusCode};
|
||||
@@ -490,12 +505,7 @@ pub mod axum {
|
||||
{
|
||||
REGISTERED_SERVER_FUNCTIONS.insert(
|
||||
(T::PATH.into(), T::InputEncoding::METHOD),
|
||||
ServerFnTraitObj::new(
|
||||
T::PATH,
|
||||
T::InputEncoding::METHOD,
|
||||
|req| Box::pin(T::run_on_server(req)),
|
||||
T::middlewares,
|
||||
),
|
||||
ServerFnTraitObj::new::<T>(|req| Box::pin(T::run_on_server(req))),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -539,7 +549,7 @@ pub mod axum {
|
||||
let key = (path.into(), method);
|
||||
REGISTERED_SERVER_FUNCTIONS.get(&key).map(|server_fn| {
|
||||
let middleware = (server_fn.middleware)();
|
||||
let mut service = BoxedService::new(server_fn.clone());
|
||||
let mut service = server_fn.clone().boxed();
|
||||
for middleware in middleware {
|
||||
service = middleware.layer(service);
|
||||
}
|
||||
@@ -578,12 +588,7 @@ pub mod actix {
|
||||
{
|
||||
REGISTERED_SERVER_FUNCTIONS.insert(
|
||||
(T::PATH.into(), T::InputEncoding::METHOD),
|
||||
ServerFnTraitObj::new(
|
||||
T::PATH,
|
||||
T::InputEncoding::METHOD,
|
||||
|req| Box::pin(T::run_on_server(req)),
|
||||
T::middlewares,
|
||||
),
|
||||
ServerFnTraitObj::new::<T>(|req| Box::pin(T::run_on_server(req))),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -603,7 +608,6 @@ pub mod actix {
|
||||
let method = req.method();
|
||||
if let Some(mut service) = get_server_fn_service(path, method) {
|
||||
service
|
||||
.0
|
||||
.run(ActixRequest::from((req, payload)))
|
||||
.await
|
||||
.0
|
||||
@@ -644,7 +648,7 @@ pub mod actix {
|
||||
REGISTERED_SERVER_FUNCTIONS.get(&(path.into(), method)).map(
|
||||
|server_fn| {
|
||||
let middleware = (server_fn.middleware)();
|
||||
let mut service = BoxedService::new(server_fn.clone());
|
||||
let mut service = server_fn.clone().boxed();
|
||||
for middleware in middleware {
|
||||
service = middleware.layer(service);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::error::ServerFnErrorErr;
|
||||
use std::{future::Future, pin::Pin};
|
||||
|
||||
/// An abstraction over a middleware layer, which can be used to add additional
|
||||
@@ -8,12 +9,31 @@ pub trait Layer<Req, Res>: Send + Sync + 'static {
|
||||
}
|
||||
|
||||
/// A type-erased service, which takes an HTTP request and returns a response.
|
||||
pub struct BoxedService<Req, Res>(pub Box<dyn Service<Req, Res> + Send>);
|
||||
pub struct BoxedService<Req, Res> {
|
||||
/// A function that converts a [`ServerFnErrorErr`] into a string.
|
||||
pub ser: fn(ServerFnErrorErr) -> String,
|
||||
/// The inner service.
|
||||
pub service: Box<dyn Service<Req, Res> + Send>,
|
||||
}
|
||||
|
||||
impl<Req, Res> BoxedService<Req, Res> {
|
||||
/// Constructs a type-erased service from this service.
|
||||
pub fn new(service: impl Service<Req, Res> + Send + 'static) -> Self {
|
||||
Self(Box::new(service))
|
||||
pub fn new(
|
||||
ser: fn(ServerFnErrorErr) -> String,
|
||||
service: impl Service<Req, Res> + Send + 'static,
|
||||
) -> Self {
|
||||
Self {
|
||||
ser,
|
||||
service: Box::new(service),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a request into a response by running the inner service.
|
||||
pub fn run(
|
||||
&mut self,
|
||||
req: Req,
|
||||
) -> Pin<Box<dyn Future<Output = Res> + Send>> {
|
||||
self.service.run(req, self.ser)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,37 +43,36 @@ pub trait Service<Request, Response> {
|
||||
fn run(
|
||||
&mut self,
|
||||
req: Request,
|
||||
ser: fn(ServerFnErrorErr) -> String,
|
||||
) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "axum-no-default")]
|
||||
mod axum {
|
||||
use super::{BoxedService, Service};
|
||||
use crate::{response::Res, ServerFnError};
|
||||
use crate::{error::ServerFnErrorErr, response::Res, ServerFnError};
|
||||
use axum::body::Body;
|
||||
use http::{Request, Response};
|
||||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
};
|
||||
use std::{future::Future, pin::Pin};
|
||||
|
||||
impl<S> super::Service<Request<Body>, Response<Body>> for S
|
||||
where
|
||||
S: tower::Service<Request<Body>, Response = Response<Body>>,
|
||||
S::Future: Send + 'static,
|
||||
S::Error: Into<ServerFnError> + Send + Debug + Display + Sync + 'static,
|
||||
S::Error: std::fmt::Display + Send + 'static,
|
||||
{
|
||||
fn run(
|
||||
&mut self,
|
||||
req: Request<Body>,
|
||||
ser: fn(ServerFnErrorErr) -> String,
|
||||
) -> Pin<Box<dyn Future<Output = Response<Body>> + Send>> {
|
||||
let path = req.uri().path().to_string();
|
||||
let inner = self.call(req);
|
||||
Box::pin(async move {
|
||||
inner.await.unwrap_or_else(|e| {
|
||||
let err = ServerFnError::new(e);
|
||||
Response::<Body>::error_response(&path, &err)
|
||||
let err =
|
||||
ser(ServerFnErrorErr::MiddlewareError(e.to_string()));
|
||||
Response::<Body>::error_response(&path, err)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -80,7 +99,7 @@ mod axum {
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Request<Body>) -> Self::Future {
|
||||
let inner = self.0.run(req);
|
||||
let inner = self.service.run(req, self.ser);
|
||||
Box::pin(async move { Ok(inner.await) })
|
||||
}
|
||||
}
|
||||
@@ -97,7 +116,7 @@ mod axum {
|
||||
&self,
|
||||
inner: BoxedService<Request<Body>, Response<Body>>,
|
||||
) -> BoxedService<Request<Body>, Response<Body>> {
|
||||
BoxedService(Box::new(self.layer(inner)))
|
||||
BoxedService::new(inner.ser, self.layer(inner))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,33 +124,31 @@ mod axum {
|
||||
#[cfg(feature = "actix")]
|
||||
mod actix {
|
||||
use crate::{
|
||||
error::ServerFnErrorErr,
|
||||
request::actix::ActixRequest,
|
||||
response::{actix::ActixResponse, Res},
|
||||
ServerFnError,
|
||||
};
|
||||
use actix_web::{HttpRequest, HttpResponse};
|
||||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
};
|
||||
use std::{future::Future, pin::Pin};
|
||||
|
||||
impl<S> super::Service<HttpRequest, HttpResponse> for S
|
||||
where
|
||||
S: actix_web::dev::Service<HttpRequest, Response = HttpResponse>,
|
||||
S::Future: Send + 'static,
|
||||
S::Error: Into<ServerFnError> + Debug + Display + 'static,
|
||||
S::Error: std::fmt::Display + Send + 'static,
|
||||
{
|
||||
fn run(
|
||||
&mut self,
|
||||
req: HttpRequest,
|
||||
ser: fn(ServerFnErrorErr) -> String,
|
||||
) -> Pin<Box<dyn Future<Output = HttpResponse> + Send>> {
|
||||
let path = req.uri().path().to_string();
|
||||
let inner = self.call(req);
|
||||
Box::pin(async move {
|
||||
inner.await.unwrap_or_else(|e| {
|
||||
let err = ServerFnError::new(e);
|
||||
ActixResponse::error_response(&path, &err).take()
|
||||
let err =
|
||||
ser(ServerFnErrorErr::MiddlewareError(e.to_string()));
|
||||
ActixResponse::error_response(&path, err).take()
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -141,18 +158,20 @@ mod actix {
|
||||
where
|
||||
S: actix_web::dev::Service<HttpRequest, Response = HttpResponse>,
|
||||
S::Future: Send + 'static,
|
||||
S::Error: Into<ServerFnError> + Debug + Display + 'static,
|
||||
S::Error: std::fmt::Display + Send + 'static,
|
||||
{
|
||||
fn run(
|
||||
&mut self,
|
||||
req: ActixRequest,
|
||||
ser: fn(ServerFnErrorErr) -> String,
|
||||
) -> Pin<Box<dyn Future<Output = ActixResponse> + Send>> {
|
||||
let path = req.0 .0.uri().path().to_string();
|
||||
let inner = self.call(req.0.take().0);
|
||||
Box::pin(async move {
|
||||
ActixResponse::from(inner.await.unwrap_or_else(|e| {
|
||||
let err = ServerFnError::new(e);
|
||||
ActixResponse::error_response(&path, &err).take()
|
||||
let err =
|
||||
ser(ServerFnErrorErr::MiddlewareError(e.to_string()));
|
||||
ActixResponse::error_response(&path, err).take()
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use crate::{error::ServerFnError, request::Req};
|
||||
use crate::{
|
||||
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
|
||||
request::Req,
|
||||
ServerFnError,
|
||||
};
|
||||
use actix_web::{web::Payload, HttpRequest};
|
||||
use bytes::Bytes;
|
||||
use futures::{Stream, StreamExt};
|
||||
@@ -33,9 +37,9 @@ impl From<(HttpRequest, Payload)> for ActixRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr> Req<CustErr> for ActixRequest
|
||||
impl<E> Req<E> for ActixRequest
|
||||
where
|
||||
CustErr: 'static,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
fn as_query(&self) -> Option<&str> {
|
||||
self.0 .0.uri().query()
|
||||
@@ -53,44 +57,39 @@ where
|
||||
self.header("Referer")
|
||||
}
|
||||
|
||||
fn try_into_bytes(
|
||||
self,
|
||||
) -> impl Future<Output = Result<Bytes, ServerFnError<CustErr>>> + Send
|
||||
{
|
||||
fn try_into_bytes(self) -> impl Future<Output = Result<Bytes, E>> + Send {
|
||||
// Actix is going to keep this on a single thread anyway so it's fine to wrap it
|
||||
// with SendWrapper, which makes it `Send` but will panic if it moves to another thread
|
||||
SendWrapper::new(async move {
|
||||
let payload = self.0.take().1;
|
||||
payload
|
||||
.to_bytes()
|
||||
.await
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
payload.to_bytes().await.map_err(|e| {
|
||||
ServerFnErrorErr::Deserialization(e.to_string())
|
||||
.into_app_error()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn try_into_string(
|
||||
self,
|
||||
) -> impl Future<Output = Result<String, ServerFnError<CustErr>>> + Send
|
||||
{
|
||||
fn try_into_string(self) -> impl Future<Output = Result<String, E>> + Send {
|
||||
// Actix is going to keep this on a single thread anyway so it's fine to wrap it
|
||||
// with SendWrapper, which makes it `Send` but will panic if it moves to another thread
|
||||
SendWrapper::new(async move {
|
||||
let payload = self.0.take().1;
|
||||
let bytes = payload
|
||||
.to_bytes()
|
||||
.await
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))?;
|
||||
String::from_utf8(bytes.into())
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
let bytes = payload.to_bytes().await.map_err(|e| {
|
||||
E::from_server_fn_error(ServerFnErrorErr::Deserialization(
|
||||
e.to_string(),
|
||||
))
|
||||
})?;
|
||||
String::from_utf8(bytes.into()).map_err(|e| {
|
||||
E::from_server_fn_error(ServerFnErrorErr::Deserialization(
|
||||
e.to_string(),
|
||||
))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn try_into_stream(
|
||||
self,
|
||||
) -> Result<
|
||||
impl Stream<Item = Result<Bytes, ServerFnError>> + Send,
|
||||
ServerFnError<CustErr>,
|
||||
> {
|
||||
) -> Result<impl Stream<Item = Result<Bytes, E>> + Send, E> {
|
||||
let payload = self.0.take().1;
|
||||
let stream = payload.map(|res| {
|
||||
res.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use crate::{error::ServerFnError, request::Req};
|
||||
use crate::{
|
||||
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
|
||||
request::Req,
|
||||
};
|
||||
use axum::body::{Body, Bytes};
|
||||
use futures::{Stream, StreamExt};
|
||||
use http::{
|
||||
@@ -8,9 +11,9 @@ use http::{
|
||||
use http_body_util::BodyExt;
|
||||
use std::borrow::Cow;
|
||||
|
||||
impl<CustErr> Req<CustErr> for Request<Body>
|
||||
impl<E> Req<E> for Request<Body>
|
||||
where
|
||||
CustErr: 'static,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
fn as_query(&self) -> Option<&str> {
|
||||
self.uri().query()
|
||||
@@ -34,29 +37,29 @@ where
|
||||
.map(|h| String::from_utf8_lossy(h.as_bytes()))
|
||||
}
|
||||
|
||||
async fn try_into_bytes(self) -> Result<Bytes, ServerFnError<CustErr>> {
|
||||
async fn try_into_bytes(self) -> Result<Bytes, E> {
|
||||
let (_parts, body) = self.into_parts();
|
||||
|
||||
body.collect()
|
||||
.await
|
||||
.map(|c| c.to_bytes())
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
body.collect().await.map(|c| c.to_bytes()).map_err(|e| {
|
||||
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
|
||||
})
|
||||
}
|
||||
|
||||
async fn try_into_string(self) -> Result<String, ServerFnError<CustErr>> {
|
||||
async fn try_into_string(self) -> Result<String, E> {
|
||||
let bytes = self.try_into_bytes().await?;
|
||||
String::from_utf8(bytes.to_vec())
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
String::from_utf8(bytes.to_vec()).map_err(|e| {
|
||||
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
|
||||
})
|
||||
}
|
||||
|
||||
fn try_into_stream(
|
||||
self,
|
||||
) -> Result<
|
||||
impl Stream<Item = Result<Bytes, ServerFnError>> + Send + 'static,
|
||||
ServerFnError<CustErr>,
|
||||
> {
|
||||
) -> Result<impl Stream<Item = Result<Bytes, E>> + Send + 'static, E> {
|
||||
Ok(self.into_body().into_data_stream().map(|chunk| {
|
||||
chunk.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
chunk.map_err(|e| {
|
||||
ServerFnErrorErr::Deserialization(e.to_string())
|
||||
.into_app_error()
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use super::ClientReq;
|
||||
use crate::{client::get_server_url, error::ServerFnError};
|
||||
use crate::{
|
||||
client::get_server_url,
|
||||
error::{FromServerFnError, ServerFnErrorErr},
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use futures::{Stream, StreamExt};
|
||||
pub use gloo_net::http::Request;
|
||||
@@ -83,7 +86,10 @@ fn abort_signal() -> (Option<AbortOnDrop>, Option<AbortSignal>) {
|
||||
(ctrl.map(|ctrl| AbortOnDrop(Some(ctrl))), signal)
|
||||
}
|
||||
|
||||
impl<CustErr> ClientReq<CustErr> for BrowserRequest {
|
||||
impl<E> ClientReq<E> for BrowserRequest
|
||||
where
|
||||
E: FromServerFnError,
|
||||
{
|
||||
type FormData = BrowserFormData;
|
||||
|
||||
fn try_new_get(
|
||||
@@ -91,7 +97,7 @@ impl<CustErr> ClientReq<CustErr> for BrowserRequest {
|
||||
accepts: &str,
|
||||
content_type: &str,
|
||||
query: &str,
|
||||
) -> Result<Self, ServerFnError<CustErr>> {
|
||||
) -> Result<Self, E> {
|
||||
let (abort_ctrl, abort_signal) = abort_signal();
|
||||
let server_url = get_server_url();
|
||||
let mut url = String::with_capacity(
|
||||
@@ -107,7 +113,11 @@ impl<CustErr> ClientReq<CustErr> for BrowserRequest {
|
||||
.header("Accept", accepts)
|
||||
.abort_signal(abort_signal.as_ref())
|
||||
.build()
|
||||
.map_err(|e| ServerFnError::Request(e.to_string()))?,
|
||||
.map_err(|e| {
|
||||
E::from_server_fn_error(ServerFnErrorErr::Request(
|
||||
e.to_string(),
|
||||
))
|
||||
})?,
|
||||
abort_ctrl,
|
||||
})))
|
||||
}
|
||||
@@ -117,7 +127,7 @@ impl<CustErr> ClientReq<CustErr> for BrowserRequest {
|
||||
accepts: &str,
|
||||
content_type: &str,
|
||||
body: String,
|
||||
) -> Result<Self, ServerFnError<CustErr>> {
|
||||
) -> Result<Self, E> {
|
||||
let (abort_ctrl, abort_signal) = abort_signal();
|
||||
let server_url = get_server_url();
|
||||
let mut url = String::with_capacity(server_url.len() + path.len());
|
||||
@@ -129,7 +139,11 @@ impl<CustErr> ClientReq<CustErr> for BrowserRequest {
|
||||
.header("Accept", accepts)
|
||||
.abort_signal(abort_signal.as_ref())
|
||||
.body(body)
|
||||
.map_err(|e| ServerFnError::Request(e.to_string()))?,
|
||||
.map_err(|e| {
|
||||
E::from_server_fn_error(ServerFnErrorErr::Request(
|
||||
e.to_string(),
|
||||
))
|
||||
})?,
|
||||
abort_ctrl,
|
||||
})))
|
||||
}
|
||||
@@ -139,7 +153,7 @@ impl<CustErr> ClientReq<CustErr> for BrowserRequest {
|
||||
accepts: &str,
|
||||
content_type: &str,
|
||||
body: Bytes,
|
||||
) -> Result<Self, ServerFnError<CustErr>> {
|
||||
) -> Result<Self, E> {
|
||||
let (abort_ctrl, abort_signal) = abort_signal();
|
||||
let server_url = get_server_url();
|
||||
let mut url = String::with_capacity(server_url.len() + path.len());
|
||||
@@ -153,7 +167,11 @@ impl<CustErr> ClientReq<CustErr> for BrowserRequest {
|
||||
.header("Accept", accepts)
|
||||
.abort_signal(abort_signal.as_ref())
|
||||
.body(body)
|
||||
.map_err(|e| ServerFnError::Request(e.to_string()))?,
|
||||
.map_err(|e| {
|
||||
E::from_server_fn_error(ServerFnErrorErr::Request(
|
||||
e.to_string(),
|
||||
))
|
||||
})?,
|
||||
abort_ctrl,
|
||||
})))
|
||||
}
|
||||
@@ -162,7 +180,7 @@ impl<CustErr> ClientReq<CustErr> for BrowserRequest {
|
||||
path: &str,
|
||||
accepts: &str,
|
||||
body: Self::FormData,
|
||||
) -> Result<Self, ServerFnError<CustErr>> {
|
||||
) -> Result<Self, E> {
|
||||
let (abort_ctrl, abort_signal) = abort_signal();
|
||||
let server_url = get_server_url();
|
||||
let mut url = String::with_capacity(server_url.len() + path.len());
|
||||
@@ -173,7 +191,11 @@ impl<CustErr> ClientReq<CustErr> for BrowserRequest {
|
||||
.header("Accept", accepts)
|
||||
.abort_signal(abort_signal.as_ref())
|
||||
.body(body.0.take())
|
||||
.map_err(|e| ServerFnError::Request(e.to_string()))?,
|
||||
.map_err(|e| {
|
||||
E::from_server_fn_error(ServerFnErrorErr::Request(
|
||||
e.to_string(),
|
||||
))
|
||||
})?,
|
||||
abort_ctrl,
|
||||
})))
|
||||
}
|
||||
@@ -183,17 +205,17 @@ impl<CustErr> ClientReq<CustErr> for BrowserRequest {
|
||||
accepts: &str,
|
||||
content_type: &str,
|
||||
body: Self::FormData,
|
||||
) -> Result<Self, ServerFnError<CustErr>> {
|
||||
) -> Result<Self, E> {
|
||||
let (abort_ctrl, abort_signal) = abort_signal();
|
||||
let form_data = body.0.take();
|
||||
let url_params =
|
||||
UrlSearchParams::new_with_str_sequence_sequence(&form_data)
|
||||
.map_err(|e| {
|
||||
ServerFnError::Serialization(e.as_string().unwrap_or_else(
|
||||
|| {
|
||||
E::from_server_fn_error(ServerFnErrorErr::Serialization(
|
||||
e.as_string().unwrap_or_else(|| {
|
||||
"Could not serialize FormData to URLSearchParams"
|
||||
.to_string()
|
||||
},
|
||||
}),
|
||||
))
|
||||
})?;
|
||||
Ok(Self(SendWrapper::new(RequestInner {
|
||||
@@ -202,7 +224,11 @@ impl<CustErr> ClientReq<CustErr> for BrowserRequest {
|
||||
.header("Accept", accepts)
|
||||
.abort_signal(abort_signal.as_ref())
|
||||
.body(url_params)
|
||||
.map_err(|e| ServerFnError::Request(e.to_string()))?,
|
||||
.map_err(|e| {
|
||||
E::from_server_fn_error(ServerFnErrorErr::Request(
|
||||
e.to_string(),
|
||||
))
|
||||
})?,
|
||||
abort_ctrl,
|
||||
})))
|
||||
}
|
||||
@@ -212,11 +238,16 @@ impl<CustErr> ClientReq<CustErr> for BrowserRequest {
|
||||
accepts: &str,
|
||||
content_type: &str,
|
||||
body: impl Stream<Item = Bytes> + 'static,
|
||||
) -> Result<Self, ServerFnError<CustErr>> {
|
||||
) -> Result<Self, E> {
|
||||
// TODO abort signal
|
||||
let (request, abort_ctrl) =
|
||||
streaming_request(path, accepts, content_type, body)
|
||||
.map_err(|e| ServerFnError::Request(format!("{e:?}")))?;
|
||||
streaming_request(path, accepts, content_type, body).map_err(
|
||||
|e| {
|
||||
E::from_server_fn_error(ServerFnErrorErr::Request(format!(
|
||||
"{e:?}"
|
||||
)))
|
||||
},
|
||||
)?;
|
||||
Ok(Self(SendWrapper::new(RequestInner {
|
||||
request,
|
||||
abort_ctrl,
|
||||
|
||||
@@ -12,7 +12,10 @@
|
||||
//! * `wasm32-wasip*` integration crate `leptos_wasi` is using this
|
||||
//! crate under the hood.
|
||||
|
||||
use crate::request::Req;
|
||||
use crate::{
|
||||
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
|
||||
request::Req,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use futures::{
|
||||
stream::{self, Stream},
|
||||
@@ -21,30 +24,23 @@ use futures::{
|
||||
use http::Request;
|
||||
use std::borrow::Cow;
|
||||
|
||||
impl<CustErr> Req<CustErr> for Request<Bytes>
|
||||
impl<E> Req<E> for Request<Bytes>
|
||||
where
|
||||
CustErr: 'static,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
async fn try_into_bytes(
|
||||
self,
|
||||
) -> Result<Bytes, crate::ServerFnError<CustErr>> {
|
||||
async fn try_into_bytes(self) -> Result<Bytes, E> {
|
||||
Ok(self.into_body())
|
||||
}
|
||||
|
||||
async fn try_into_string(
|
||||
self,
|
||||
) -> Result<String, crate::ServerFnError<CustErr>> {
|
||||
async fn try_into_string(self) -> Result<String, E> {
|
||||
String::from_utf8(self.into_body().into()).map_err(|err| {
|
||||
crate::ServerFnError::Deserialization(err.to_string())
|
||||
ServerFnErrorErr::Deserialization(err.to_string()).into_app_error()
|
||||
})
|
||||
}
|
||||
|
||||
fn try_into_stream(
|
||||
self,
|
||||
) -> Result<
|
||||
impl Stream<Item = Result<Bytes, crate::ServerFnError>> + Send + 'static,
|
||||
crate::ServerFnError<CustErr>,
|
||||
> {
|
||||
) -> Result<impl Stream<Item = Result<Bytes, E>> + Send + 'static, E> {
|
||||
Ok(stream::iter(self.into_body())
|
||||
.ready_chunks(16)
|
||||
.map(|chunk| Ok(Bytes::from(chunk))))
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::error::ServerFnError;
|
||||
use bytes::Bytes;
|
||||
use futures::Stream;
|
||||
use std::{borrow::Cow, future::Future};
|
||||
@@ -19,7 +18,7 @@ pub mod generic;
|
||||
pub mod reqwest;
|
||||
|
||||
/// Represents a request as made by the client.
|
||||
pub trait ClientReq<CustErr>
|
||||
pub trait ClientReq<E>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -32,7 +31,7 @@ where
|
||||
content_type: &str,
|
||||
accepts: &str,
|
||||
query: &str,
|
||||
) -> Result<Self, ServerFnError<CustErr>>;
|
||||
) -> Result<Self, E>;
|
||||
|
||||
/// Attempts to construct a new `POST` request with a text body.
|
||||
fn try_new_post(
|
||||
@@ -40,7 +39,7 @@ where
|
||||
content_type: &str,
|
||||
accepts: &str,
|
||||
body: String,
|
||||
) -> Result<Self, ServerFnError<CustErr>>;
|
||||
) -> Result<Self, E>;
|
||||
|
||||
/// Attempts to construct a new `POST` request with a binary body.
|
||||
fn try_new_post_bytes(
|
||||
@@ -48,7 +47,7 @@ where
|
||||
content_type: &str,
|
||||
accepts: &str,
|
||||
body: Bytes,
|
||||
) -> Result<Self, ServerFnError<CustErr>>;
|
||||
) -> Result<Self, E>;
|
||||
|
||||
/// Attempts to construct a new `POST` request with form data as the body.
|
||||
fn try_new_post_form_data(
|
||||
@@ -56,14 +55,14 @@ where
|
||||
accepts: &str,
|
||||
content_type: &str,
|
||||
body: Self::FormData,
|
||||
) -> Result<Self, ServerFnError<CustErr>>;
|
||||
) -> Result<Self, E>;
|
||||
|
||||
/// Attempts to construct a new `POST` request with a multipart body.
|
||||
fn try_new_multipart(
|
||||
path: &str,
|
||||
accepts: &str,
|
||||
body: Self::FormData,
|
||||
) -> Result<Self, ServerFnError<CustErr>>;
|
||||
) -> Result<Self, E>;
|
||||
|
||||
/// Attempts to construct a new `POST` request with a streaming body.
|
||||
fn try_new_streaming(
|
||||
@@ -71,11 +70,11 @@ where
|
||||
accepts: &str,
|
||||
content_type: &str,
|
||||
body: impl Stream<Item = Bytes> + Send + 'static,
|
||||
) -> Result<Self, ServerFnError<CustErr>>;
|
||||
) -> Result<Self, E>;
|
||||
}
|
||||
|
||||
/// Represents the request as received by the server.
|
||||
pub trait Req<CustErr>
|
||||
pub trait Req<E>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -92,32 +91,22 @@ where
|
||||
fn referer(&self) -> Option<Cow<'_, str>>;
|
||||
|
||||
/// Attempts to extract the body of the request into [`Bytes`].
|
||||
fn try_into_bytes(
|
||||
self,
|
||||
) -> impl Future<Output = Result<Bytes, ServerFnError<CustErr>>> + Send;
|
||||
fn try_into_bytes(self) -> impl Future<Output = Result<Bytes, E>> + Send;
|
||||
|
||||
/// Attempts to convert the body of the request into a string.
|
||||
fn try_into_string(
|
||||
self,
|
||||
) -> impl Future<Output = Result<String, ServerFnError<CustErr>>> + Send;
|
||||
fn try_into_string(self) -> impl Future<Output = Result<String, E>> + Send;
|
||||
|
||||
/// Attempts to convert the body of the request into a stream of bytes.
|
||||
fn try_into_stream(
|
||||
self,
|
||||
) -> Result<
|
||||
impl Stream<Item = Result<Bytes, ServerFnError>> + Send + 'static,
|
||||
ServerFnError<CustErr>,
|
||||
>;
|
||||
) -> Result<impl Stream<Item = Result<Bytes, E>> + Send + 'static, E>;
|
||||
}
|
||||
|
||||
/// A mocked request type that can be used in place of the actual server request,
|
||||
/// when compiling for the browser.
|
||||
pub struct BrowserMockReq;
|
||||
|
||||
impl<CustErr> Req<CustErr> for BrowserMockReq
|
||||
where
|
||||
CustErr: 'static,
|
||||
{
|
||||
impl<E: 'static> Req<E> for BrowserMockReq {
|
||||
fn as_query(&self) -> Option<&str> {
|
||||
unreachable!()
|
||||
}
|
||||
@@ -133,20 +122,17 @@ where
|
||||
fn referer(&self) -> Option<Cow<'_, str>> {
|
||||
unreachable!()
|
||||
}
|
||||
async fn try_into_bytes(self) -> Result<Bytes, ServerFnError<CustErr>> {
|
||||
async fn try_into_bytes(self) -> Result<Bytes, E> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
async fn try_into_string(self) -> Result<String, ServerFnError<CustErr>> {
|
||||
async fn try_into_string(self) -> Result<String, E> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn try_into_stream(
|
||||
self,
|
||||
) -> Result<
|
||||
impl Stream<Item = Result<Bytes, ServerFnError>> + Send,
|
||||
ServerFnError<CustErr>,
|
||||
> {
|
||||
) -> Result<impl Stream<Item = Result<Bytes, E>> + Send, E> {
|
||||
Ok(futures::stream::once(async { unreachable!() }))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use super::ClientReq;
|
||||
use crate::{client::get_server_url, error::ServerFnError};
|
||||
use crate::{
|
||||
client::get_server_url,
|
||||
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use futures::Stream;
|
||||
use once_cell::sync::Lazy;
|
||||
@@ -8,7 +11,10 @@ pub use reqwest::{multipart::Form, Client, Method, Request, Url};
|
||||
|
||||
pub(crate) static CLIENT: Lazy<Client> = Lazy::new(Client::new);
|
||||
|
||||
impl<CustErr> ClientReq<CustErr> for Request {
|
||||
impl<E> ClientReq<E> for Request
|
||||
where
|
||||
E: FromServerFnError,
|
||||
{
|
||||
type FormData = Form;
|
||||
|
||||
fn try_new_get(
|
||||
@@ -16,17 +22,22 @@ impl<CustErr> ClientReq<CustErr> for Request {
|
||||
accepts: &str,
|
||||
content_type: &str,
|
||||
query: &str,
|
||||
) -> Result<Self, ServerFnError<CustErr>> {
|
||||
) -> Result<Self, E> {
|
||||
let url = format!("{}{}", get_server_url(), path);
|
||||
let mut url = Url::try_from(url.as_str())
|
||||
.map_err(|e| ServerFnError::Request(e.to_string()))?;
|
||||
let mut url = Url::try_from(url.as_str()).map_err(|e| {
|
||||
E::from_server_fn_error(ServerFnErrorErr::Request(e.to_string()))
|
||||
})?;
|
||||
url.set_query(Some(query));
|
||||
let req = CLIENT
|
||||
.get(url)
|
||||
.header(CONTENT_TYPE, content_type)
|
||||
.header(ACCEPT, accepts)
|
||||
.build()
|
||||
.map_err(|e| ServerFnError::Request(e.to_string()))?;
|
||||
.map_err(|e| {
|
||||
E::from_server_fn_error(ServerFnErrorErr::Request(
|
||||
e.to_string(),
|
||||
))
|
||||
})?;
|
||||
Ok(req)
|
||||
}
|
||||
|
||||
@@ -35,7 +46,7 @@ impl<CustErr> ClientReq<CustErr> for Request {
|
||||
accepts: &str,
|
||||
content_type: &str,
|
||||
body: String,
|
||||
) -> Result<Self, ServerFnError<CustErr>> {
|
||||
) -> Result<Self, E> {
|
||||
let url = format!("{}{}", get_server_url(), path);
|
||||
CLIENT
|
||||
.post(url)
|
||||
@@ -43,7 +54,9 @@ impl<CustErr> ClientReq<CustErr> for Request {
|
||||
.header(ACCEPT, accepts)
|
||||
.body(body)
|
||||
.build()
|
||||
.map_err(|e| ServerFnError::Request(e.to_string()))
|
||||
.map_err(|e| {
|
||||
ServerFnErrorErr::Request(e.to_string()).into_app_error()
|
||||
})
|
||||
}
|
||||
|
||||
fn try_new_post_bytes(
|
||||
@@ -51,7 +64,7 @@ impl<CustErr> ClientReq<CustErr> for Request {
|
||||
accepts: &str,
|
||||
content_type: &str,
|
||||
body: Bytes,
|
||||
) -> Result<Self, ServerFnError<CustErr>> {
|
||||
) -> Result<Self, E> {
|
||||
let url = format!("{}{}", get_server_url(), path);
|
||||
CLIENT
|
||||
.post(url)
|
||||
@@ -59,20 +72,24 @@ impl<CustErr> ClientReq<CustErr> for Request {
|
||||
.header(ACCEPT, accepts)
|
||||
.body(body)
|
||||
.build()
|
||||
.map_err(|e| ServerFnError::Request(e.to_string()))
|
||||
.map_err(|e| {
|
||||
ServerFnErrorErr::Request(e.to_string()).into_app_error()
|
||||
})
|
||||
}
|
||||
|
||||
fn try_new_multipart(
|
||||
path: &str,
|
||||
accepts: &str,
|
||||
body: Self::FormData,
|
||||
) -> Result<Self, ServerFnError<CustErr>> {
|
||||
) -> Result<Self, E> {
|
||||
CLIENT
|
||||
.post(path)
|
||||
.header(ACCEPT, accepts)
|
||||
.multipart(body)
|
||||
.build()
|
||||
.map_err(|e| ServerFnError::Request(e.to_string()))
|
||||
.map_err(|e| {
|
||||
ServerFnErrorErr::Request(e.to_string()).into_app_error()
|
||||
})
|
||||
}
|
||||
|
||||
fn try_new_post_form_data(
|
||||
@@ -80,14 +97,16 @@ impl<CustErr> ClientReq<CustErr> for Request {
|
||||
accepts: &str,
|
||||
content_type: &str,
|
||||
body: Self::FormData,
|
||||
) -> Result<Self, ServerFnError<CustErr>> {
|
||||
) -> Result<Self, E> {
|
||||
CLIENT
|
||||
.post(path)
|
||||
.header(CONTENT_TYPE, content_type)
|
||||
.header(ACCEPT, accepts)
|
||||
.multipart(body)
|
||||
.build()
|
||||
.map_err(|e| ServerFnError::Request(e.to_string()))
|
||||
.map_err(|e| {
|
||||
ServerFnErrorErr::Request(e.to_string()).into_app_error()
|
||||
})
|
||||
}
|
||||
|
||||
fn try_new_streaming(
|
||||
@@ -95,7 +114,7 @@ impl<CustErr> ClientReq<CustErr> for Request {
|
||||
_accepts: &str,
|
||||
_content_type: &str,
|
||||
_body: impl Stream<Item = Bytes> + 'static,
|
||||
) -> Result<Self, ServerFnError<CustErr>> {
|
||||
) -> Result<Self, E> {
|
||||
todo!("Streaming requests are not yet implemented for reqwest.")
|
||||
// We run into a fundamental issue here.
|
||||
// To be a reqwest body, the type must be Sync
|
||||
@@ -112,7 +131,7 @@ impl<CustErr> ClientReq<CustErr> for Request {
|
||||
.header(ACCEPT, accepts)
|
||||
.body(body)
|
||||
.build()
|
||||
.map_err(|e| ServerFnError::Request(e.to_string()))
|
||||
.map_err(|e| ServerFnErrorErr::Request(e.to_string()).into())
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use http::{
|
||||
use http_body_util::BodyExt;
|
||||
use std::borrow::Cow;
|
||||
|
||||
impl<CustErr> Req<CustErr> for IncomingRequest
|
||||
impl<E> Req<E> for IncomingRequest
|
||||
where
|
||||
CustErr: 'static,
|
||||
{
|
||||
@@ -34,29 +34,31 @@ where
|
||||
.map(|h| String::from_utf8_lossy(h.as_bytes()))
|
||||
}
|
||||
|
||||
async fn try_into_bytes(self) -> Result<Bytes, ServerFnError<CustErr>> {
|
||||
async fn try_into_bytes(self) -> Result<Bytes, E> {
|
||||
let (_parts, body) = self.into_parts();
|
||||
|
||||
body.collect()
|
||||
.await
|
||||
.map(|c| c.to_bytes())
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
body.collect().await.map(|c| c.to_bytes()).map_err(|e| {
|
||||
ServerFnErrorErr::Deserialization(e.to_string()).into()
|
||||
})
|
||||
}
|
||||
|
||||
async fn try_into_string(self) -> Result<String, ServerFnError<CustErr>> {
|
||||
async fn try_into_string(self) -> Result<String, E> {
|
||||
let bytes = self.try_into_bytes().await?;
|
||||
String::from_utf8(bytes.to_vec())
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
String::from_utf8(bytes.to_vec()).map_err(|e| {
|
||||
ServerFnErrorErr::Deserialization(e.to_string()).into()
|
||||
})
|
||||
}
|
||||
|
||||
fn try_into_stream(
|
||||
self,
|
||||
) -> Result<
|
||||
impl Stream<Item = Result<Bytes, ServerFnError>> + Send + 'static,
|
||||
ServerFnError<CustErr>,
|
||||
E,
|
||||
> {
|
||||
Ok(self.into_body().into_data_stream().map(|chunk| {
|
||||
chunk.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
chunk.map_err(|e| {
|
||||
ServerFnErrorErr::Deserialization(e.to_string()).into()
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::Res;
|
||||
use super::{Res, TryRes};
|
||||
use crate::error::{
|
||||
ServerFnError, ServerFnErrorErr, ServerFnErrorSerde, SERVER_FN_ERROR_HEADER,
|
||||
FromServerFnError, ServerFnErrorWrapper, SERVER_FN_ERROR_HEADER,
|
||||
};
|
||||
use actix_web::{
|
||||
http::{
|
||||
@@ -13,10 +13,6 @@ use actix_web::{
|
||||
use bytes::Bytes;
|
||||
use futures::{Stream, StreamExt};
|
||||
use send_wrapper::SendWrapper;
|
||||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
/// A wrapped Actix response.
|
||||
///
|
||||
@@ -38,14 +34,11 @@ impl From<HttpResponse> for ActixResponse {
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr> Res<CustErr> for ActixResponse
|
||||
impl<E> TryRes<E> for ActixResponse
|
||||
where
|
||||
CustErr: FromStr + Display + Debug + 'static,
|
||||
E: FromServerFnError,
|
||||
{
|
||||
fn try_from_string(
|
||||
content_type: &str,
|
||||
data: String,
|
||||
) -> Result<Self, ServerFnError<CustErr>> {
|
||||
fn try_from_string(content_type: &str, data: String) -> Result<Self, E> {
|
||||
let mut builder = HttpResponse::build(StatusCode::OK);
|
||||
Ok(ActixResponse(SendWrapper::new(
|
||||
builder
|
||||
@@ -54,10 +47,7 @@ where
|
||||
)))
|
||||
}
|
||||
|
||||
fn try_from_bytes(
|
||||
content_type: &str,
|
||||
data: Bytes,
|
||||
) -> Result<Self, ServerFnError<CustErr>> {
|
||||
fn try_from_bytes(content_type: &str, data: Bytes) -> Result<Self, E> {
|
||||
let mut builder = HttpResponse::build(StatusCode::OK);
|
||||
Ok(ActixResponse(SendWrapper::new(
|
||||
builder
|
||||
@@ -68,23 +58,23 @@ where
|
||||
|
||||
fn try_from_stream(
|
||||
content_type: &str,
|
||||
data: impl Stream<Item = Result<Bytes, ServerFnError<CustErr>>> + 'static,
|
||||
) -> Result<Self, ServerFnError<CustErr>> {
|
||||
data: impl Stream<Item = Result<Bytes, E>> + 'static,
|
||||
) -> Result<Self, E> {
|
||||
let mut builder = HttpResponse::build(StatusCode::OK);
|
||||
Ok(ActixResponse(SendWrapper::new(
|
||||
builder
|
||||
.insert_header((header::CONTENT_TYPE, content_type))
|
||||
.streaming(
|
||||
data.map(|data| data.map_err(ServerFnErrorErr::from)),
|
||||
),
|
||||
.streaming(data.map(|data| data.map_err(ServerFnErrorWrapper))),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
fn error_response(path: &str, err: &ServerFnError<CustErr>) -> Self {
|
||||
impl Res for ActixResponse {
|
||||
fn error_response(path: &str, err: String) -> Self {
|
||||
ActixResponse(SendWrapper::new(
|
||||
HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.append_header((SERVER_FN_ERROR_HEADER, path))
|
||||
.body(err.ser().unwrap_or_else(|_| err.to_string())),
|
||||
.body(err),
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use super::ClientRes;
|
||||
use crate::{error::ServerFnError, redirect::REDIRECT_HEADER};
|
||||
use crate::{
|
||||
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
|
||||
redirect::REDIRECT_HEADER,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use futures::{Stream, StreamExt};
|
||||
pub use gloo_net::http::Response;
|
||||
@@ -12,48 +15,39 @@ use wasm_streams::ReadableStream;
|
||||
/// The response to a `fetch` request made in the browser.
|
||||
pub struct BrowserResponse(pub(crate) SendWrapper<Response>);
|
||||
|
||||
impl<CustErr> ClientRes<CustErr> for BrowserResponse {
|
||||
fn try_into_string(
|
||||
self,
|
||||
) -> impl Future<Output = Result<String, ServerFnError<CustErr>>> + Send
|
||||
{
|
||||
impl<E: FromServerFnError> ClientRes<E> for BrowserResponse {
|
||||
fn try_into_string(self) -> impl Future<Output = Result<String, E>> + Send {
|
||||
// the browser won't send this async work between threads (because it's single-threaded)
|
||||
// so we can safely wrap this
|
||||
SendWrapper::new(async move {
|
||||
self.0
|
||||
.text()
|
||||
.await
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
self.0.text().await.map_err(|e| {
|
||||
ServerFnErrorErr::Deserialization(e.to_string())
|
||||
.into_app_error()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn try_into_bytes(
|
||||
self,
|
||||
) -> impl Future<Output = Result<Bytes, ServerFnError<CustErr>>> + Send
|
||||
{
|
||||
fn try_into_bytes(self) -> impl Future<Output = Result<Bytes, E>> + Send {
|
||||
// the browser won't send this async work between threads (because it's single-threaded)
|
||||
// so we can safely wrap this
|
||||
SendWrapper::new(async move {
|
||||
self.0
|
||||
.binary()
|
||||
.await
|
||||
.map(Bytes::from)
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
self.0.binary().await.map(Bytes::from).map_err(|e| {
|
||||
ServerFnErrorErr::Deserialization(e.to_string())
|
||||
.into_app_error()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn try_into_stream(
|
||||
self,
|
||||
) -> Result<
|
||||
impl Stream<Item = Result<Bytes, ServerFnError>> + Send + 'static,
|
||||
ServerFnError<CustErr>,
|
||||
> {
|
||||
) -> Result<impl Stream<Item = Result<Bytes, E>> + Send + 'static, E> {
|
||||
let stream = ReadableStream::from_raw(self.0.body().unwrap())
|
||||
.into_stream()
|
||||
.map(|data| match data {
|
||||
Err(e) => {
|
||||
web_sys::console::error_1(&e);
|
||||
Err(ServerFnError::Request(format!("{e:?}")))
|
||||
Err(ServerFnErrorErr::Request(format!("{e:?}"))
|
||||
.into_app_error())
|
||||
}
|
||||
Ok(data) => {
|
||||
let data = data.unchecked_into::<Uint8Array>();
|
||||
|
||||
@@ -12,18 +12,15 @@
|
||||
//! * `wasm32-wasip*` integration crate `leptos_wasi` is using this
|
||||
//! crate under the hood.
|
||||
|
||||
use super::Res;
|
||||
use super::{Res, TryRes};
|
||||
use crate::error::{
|
||||
ServerFnError, ServerFnErrorErr, ServerFnErrorSerde, SERVER_FN_ERROR_HEADER,
|
||||
FromServerFnError, IntoAppError, ServerFnErrorErr, ServerFnErrorWrapper,
|
||||
SERVER_FN_ERROR_HEADER,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use futures::{Stream, TryStreamExt};
|
||||
use http::{header, HeaderValue, Response, StatusCode};
|
||||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
pin::Pin,
|
||||
str::FromStr,
|
||||
};
|
||||
use std::pin::Pin;
|
||||
use throw_error::Error;
|
||||
|
||||
/// The Body of a Response whose *execution model* can be
|
||||
@@ -44,55 +41,55 @@ impl From<String> for Body {
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr> Res<CustErr> for Response<Body>
|
||||
impl<E> TryRes<E> for Response<Body>
|
||||
where
|
||||
CustErr: Send + Sync + Debug + FromStr + Display + 'static,
|
||||
E: Send + Sync + FromServerFnError,
|
||||
{
|
||||
fn try_from_string(
|
||||
content_type: &str,
|
||||
data: String,
|
||||
) -> Result<Self, ServerFnError<CustErr>> {
|
||||
fn try_from_string(content_type: &str, data: String) -> Result<Self, E> {
|
||||
let builder = http::Response::builder();
|
||||
builder
|
||||
.status(200)
|
||||
.header(http::header::CONTENT_TYPE, content_type)
|
||||
.body(data.into())
|
||||
.map_err(|e| ServerFnError::Response(e.to_string()))
|
||||
.map_err(|e| {
|
||||
ServerFnErrorErr::Response(e.to_string()).into_app_error()
|
||||
})
|
||||
}
|
||||
|
||||
fn try_from_bytes(
|
||||
content_type: &str,
|
||||
data: Bytes,
|
||||
) -> Result<Self, ServerFnError<CustErr>> {
|
||||
fn try_from_bytes(content_type: &str, data: Bytes) -> Result<Self, E> {
|
||||
let builder = http::Response::builder();
|
||||
builder
|
||||
.status(200)
|
||||
.header(http::header::CONTENT_TYPE, content_type)
|
||||
.body(Body::Sync(data))
|
||||
.map_err(|e| ServerFnError::Response(e.to_string()))
|
||||
.map_err(|e| {
|
||||
ServerFnErrorErr::Response(e.to_string()).into_app_error()
|
||||
})
|
||||
}
|
||||
|
||||
fn try_from_stream(
|
||||
content_type: &str,
|
||||
data: impl Stream<Item = Result<Bytes, ServerFnError<CustErr>>>
|
||||
+ Send
|
||||
+ 'static,
|
||||
) -> Result<Self, ServerFnError<CustErr>> {
|
||||
data: impl Stream<Item = Result<Bytes, E>> + Send + 'static,
|
||||
) -> Result<Self, E> {
|
||||
let builder = http::Response::builder();
|
||||
builder
|
||||
.status(200)
|
||||
.header(http::header::CONTENT_TYPE, content_type)
|
||||
.body(Body::Async(Box::pin(
|
||||
data.map_err(ServerFnErrorErr::from).map_err(Error::from),
|
||||
data.map_err(ServerFnErrorWrapper).map_err(Error::from),
|
||||
)))
|
||||
.map_err(|e| ServerFnError::Response(e.to_string()))
|
||||
.map_err(|e| {
|
||||
ServerFnErrorErr::Response(e.to_string()).into_app_error()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn error_response(path: &str, err: &ServerFnError<CustErr>) -> Self {
|
||||
impl Res for Response<Body> {
|
||||
fn error_response(path: &str, err: String) -> Self {
|
||||
Response::builder()
|
||||
.status(http::StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.header(SERVER_FN_ERROR_HEADER, path)
|
||||
.body(err.ser().unwrap_or_else(|_| err.to_string()).into())
|
||||
.body(err.into())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,65 +1,61 @@
|
||||
use super::Res;
|
||||
use super::{Res, TryRes};
|
||||
use crate::error::{
|
||||
ServerFnError, ServerFnErrorErr, ServerFnErrorSerde, SERVER_FN_ERROR_HEADER,
|
||||
FromServerFnError, IntoAppError, ServerFnErrorErr, ServerFnErrorWrapper,
|
||||
SERVER_FN_ERROR_HEADER,
|
||||
};
|
||||
use axum::body::Body;
|
||||
use bytes::Bytes;
|
||||
use futures::{Stream, StreamExt};
|
||||
use futures::{Stream, TryStreamExt};
|
||||
use http::{header, HeaderValue, Response, StatusCode};
|
||||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
impl<CustErr> Res<CustErr> for Response<Body>
|
||||
impl<E> TryRes<E> for Response<Body>
|
||||
where
|
||||
CustErr: Send + Sync + Debug + FromStr + Display + 'static,
|
||||
E: Send + Sync + FromServerFnError,
|
||||
{
|
||||
fn try_from_string(
|
||||
content_type: &str,
|
||||
data: String,
|
||||
) -> Result<Self, ServerFnError<CustErr>> {
|
||||
fn try_from_string(content_type: &str, data: String) -> Result<Self, E> {
|
||||
let builder = http::Response::builder();
|
||||
builder
|
||||
.status(200)
|
||||
.header(http::header::CONTENT_TYPE, content_type)
|
||||
.body(Body::from(data))
|
||||
.map_err(|e| ServerFnError::Response(e.to_string()))
|
||||
.map_err(|e| {
|
||||
ServerFnErrorErr::Response(e.to_string()).into_app_error()
|
||||
})
|
||||
}
|
||||
|
||||
fn try_from_bytes(
|
||||
content_type: &str,
|
||||
data: Bytes,
|
||||
) -> Result<Self, ServerFnError<CustErr>> {
|
||||
fn try_from_bytes(content_type: &str, data: Bytes) -> Result<Self, E> {
|
||||
let builder = http::Response::builder();
|
||||
builder
|
||||
.status(200)
|
||||
.header(http::header::CONTENT_TYPE, content_type)
|
||||
.body(Body::from(data))
|
||||
.map_err(|e| ServerFnError::Response(e.to_string()))
|
||||
.map_err(|e| {
|
||||
ServerFnErrorErr::Response(e.to_string()).into_app_error()
|
||||
})
|
||||
}
|
||||
|
||||
fn try_from_stream(
|
||||
content_type: &str,
|
||||
data: impl Stream<Item = Result<Bytes, ServerFnError<CustErr>>>
|
||||
+ Send
|
||||
+ 'static,
|
||||
) -> Result<Self, ServerFnError<CustErr>> {
|
||||
let body =
|
||||
Body::from_stream(data.map(|n| n.map_err(ServerFnErrorErr::from)));
|
||||
data: impl Stream<Item = Result<Bytes, E>> + Send + 'static,
|
||||
) -> Result<Self, E> {
|
||||
let body = Body::from_stream(data.map_err(|e| ServerFnErrorWrapper(e)));
|
||||
let builder = http::Response::builder();
|
||||
builder
|
||||
.status(200)
|
||||
.header(http::header::CONTENT_TYPE, content_type)
|
||||
.body(body)
|
||||
.map_err(|e| ServerFnError::Response(e.to_string()))
|
||||
.map_err(|e| {
|
||||
ServerFnErrorErr::Response(e.to_string()).into_app_error()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn error_response(path: &str, err: &ServerFnError<CustErr>) -> Self {
|
||||
impl Res for Response<Body> {
|
||||
fn error_response(path: &str, err: String) -> Self {
|
||||
Response::builder()
|
||||
.status(http::StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.header(SERVER_FN_ERROR_HEADER, path)
|
||||
.body(err.ser().unwrap_or_else(|_| err.to_string()).into())
|
||||
.body(err.into())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
|
||||
@@ -13,62 +13,49 @@ pub mod http;
|
||||
#[cfg(feature = "reqwest")]
|
||||
pub mod reqwest;
|
||||
|
||||
use crate::error::ServerFnError;
|
||||
use bytes::Bytes;
|
||||
use futures::Stream;
|
||||
use std::future::Future;
|
||||
|
||||
/// Represents the response as created by the server;
|
||||
pub trait Res<CustErr>
|
||||
pub trait TryRes<E>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
/// Attempts to convert a UTF-8 string into an HTTP response.
|
||||
fn try_from_string(
|
||||
content_type: &str,
|
||||
data: String,
|
||||
) -> Result<Self, ServerFnError<CustErr>>;
|
||||
fn try_from_string(content_type: &str, data: String) -> Result<Self, E>;
|
||||
|
||||
/// Attempts to convert a binary blob represented as bytes into an HTTP response.
|
||||
fn try_from_bytes(
|
||||
content_type: &str,
|
||||
data: Bytes,
|
||||
) -> Result<Self, ServerFnError<CustErr>>;
|
||||
fn try_from_bytes(content_type: &str, data: Bytes) -> Result<Self, E>;
|
||||
|
||||
/// Attempts to convert a stream of bytes into an HTTP response.
|
||||
fn try_from_stream(
|
||||
content_type: &str,
|
||||
data: impl Stream<Item = Result<Bytes, ServerFnError<CustErr>>>
|
||||
+ Send
|
||||
+ 'static,
|
||||
) -> Result<Self, ServerFnError<CustErr>>;
|
||||
data: impl Stream<Item = Result<Bytes, E>> + Send + 'static,
|
||||
) -> Result<Self, E>;
|
||||
}
|
||||
|
||||
/// Represents the response as created by the server;
|
||||
pub trait Res {
|
||||
/// Converts an error into a response, with a `500` status code and the error text as its body.
|
||||
fn error_response(path: &str, err: &ServerFnError<CustErr>) -> Self;
|
||||
fn error_response(path: &str, err: String) -> Self;
|
||||
|
||||
/// Redirect the response by setting a 302 code and Location header.
|
||||
fn redirect(&mut self, path: &str);
|
||||
}
|
||||
|
||||
/// Represents the response as received by the client.
|
||||
pub trait ClientRes<CustErr> {
|
||||
pub trait ClientRes<E> {
|
||||
/// Attempts to extract a UTF-8 string from an HTTP response.
|
||||
fn try_into_string(
|
||||
self,
|
||||
) -> impl Future<Output = Result<String, ServerFnError<CustErr>>> + Send;
|
||||
fn try_into_string(self) -> impl Future<Output = Result<String, E>> + Send;
|
||||
|
||||
/// Attempts to extract a binary blob from an HTTP response.
|
||||
fn try_into_bytes(
|
||||
self,
|
||||
) -> impl Future<Output = Result<Bytes, ServerFnError<CustErr>>> + Send;
|
||||
fn try_into_bytes(self) -> impl Future<Output = Result<Bytes, E>> + Send;
|
||||
|
||||
/// Attempts to extract a binary stream from an HTTP response.
|
||||
fn try_into_stream(
|
||||
self,
|
||||
) -> Result<
|
||||
impl Stream<Item = Result<Bytes, ServerFnError>> + Send + Sync + 'static,
|
||||
ServerFnError<CustErr>,
|
||||
>;
|
||||
) -> Result<impl Stream<Item = Result<Bytes, E>> + Send + Sync + 'static, E>;
|
||||
|
||||
/// HTTP status code of the response.
|
||||
fn status(&self) -> u16;
|
||||
@@ -91,29 +78,25 @@ pub trait ClientRes<CustErr> {
|
||||
/// server response type when compiling for the client.
|
||||
pub struct BrowserMockRes;
|
||||
|
||||
impl<CustErr> Res<CustErr> for BrowserMockRes {
|
||||
fn try_from_string(
|
||||
_content_type: &str,
|
||||
_data: String,
|
||||
) -> Result<Self, ServerFnError<CustErr>> {
|
||||
impl<E> TryRes<E> for BrowserMockRes {
|
||||
fn try_from_string(_content_type: &str, _data: String) -> Result<Self, E> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn try_from_bytes(
|
||||
_content_type: &str,
|
||||
_data: Bytes,
|
||||
) -> Result<Self, ServerFnError<CustErr>> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn error_response(_path: &str, _err: &ServerFnError<CustErr>) -> Self {
|
||||
fn try_from_bytes(_content_type: &str, _data: Bytes) -> Result<Self, E> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn try_from_stream(
|
||||
_content_type: &str,
|
||||
_data: impl Stream<Item = Result<Bytes, ServerFnError<CustErr>>>,
|
||||
) -> Result<Self, ServerFnError<CustErr>> {
|
||||
_data: impl Stream<Item = Result<Bytes, E>>,
|
||||
) -> Result<Self, E> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Res for BrowserMockRes {
|
||||
fn error_response(_path: &str, _err: String) -> Self {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +1,28 @@
|
||||
use super::ClientRes;
|
||||
use crate::error::ServerFnError;
|
||||
use crate::error::{FromServerFnError, IntoAppError, ServerFnErrorErr};
|
||||
use bytes::Bytes;
|
||||
use futures::{Stream, TryStreamExt};
|
||||
use reqwest::Response;
|
||||
|
||||
impl<CustErr> ClientRes<CustErr> for Response {
|
||||
async fn try_into_string(self) -> Result<String, ServerFnError<CustErr>> {
|
||||
self.text()
|
||||
.await
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
impl<E: FromServerFnError> ClientRes<E> for Response {
|
||||
async fn try_into_string(self) -> Result<String, E> {
|
||||
self.text().await.map_err(|e| {
|
||||
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
|
||||
})
|
||||
}
|
||||
|
||||
async fn try_into_bytes(self) -> Result<Bytes, ServerFnError<CustErr>> {
|
||||
self.bytes()
|
||||
.await
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
async fn try_into_bytes(self) -> Result<Bytes, E> {
|
||||
self.bytes().await.map_err(|e| {
|
||||
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
|
||||
})
|
||||
}
|
||||
|
||||
fn try_into_stream(
|
||||
self,
|
||||
) -> Result<
|
||||
impl Stream<Item = Result<Bytes, ServerFnError>> + Send + 'static,
|
||||
ServerFnError<CustErr>,
|
||||
> {
|
||||
Ok(self
|
||||
.bytes_stream()
|
||||
.map_err(|e| ServerFnError::Response(e.to_string())))
|
||||
) -> Result<impl Stream<Item = Result<Bytes, E>> + Send + 'static, E> {
|
||||
Ok(self.bytes_stream().map_err(|e| {
|
||||
ServerFnErrorErr::Response(e.to_string()).into_app_error()
|
||||
}))
|
||||
}
|
||||
|
||||
fn status(&self) -> u16 {
|
||||
|
||||
@@ -230,6 +230,7 @@ pub fn server_macro_impl(
|
||||
None => Some("PostUrl".to_string()),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let input = input
|
||||
.map(|n| {
|
||||
if builtin_encoding {
|
||||
@@ -382,13 +383,8 @@ pub fn server_macro_impl(
|
||||
quote! {
|
||||
#server_fn_path::inventory::submit! {{
|
||||
use #server_fn_path::{ServerFn, codec::Encoding};
|
||||
#server_fn_path::ServerFnTraitObj::new(
|
||||
#wrapped_struct_name_turbofish::PATH,
|
||||
<#wrapped_struct_name as ServerFn>::InputEncoding::METHOD,
|
||||
|req| {
|
||||
Box::pin(#wrapped_struct_name_turbofish::run_on_server(req))
|
||||
},
|
||||
#wrapped_struct_name_turbofish::middlewares
|
||||
#server_fn_path::ServerFnTraitObj::new::<#wrapped_struct_name>(
|
||||
|req| Box::pin(#wrapped_struct_name_turbofish::run_on_server(req)),
|
||||
)
|
||||
}}
|
||||
}
|
||||
@@ -609,20 +605,44 @@ pub fn server_macro_impl(
|
||||
} else {
|
||||
quote! { concat!("/", #fn_path) }
|
||||
};
|
||||
|
||||
let enable_server_fn_mod_path = option_env!("SERVER_FN_MOD_PATH").is_some();
|
||||
let mod_path = if enable_server_fn_mod_path {
|
||||
quote! {
|
||||
#server_fn_path::const_format::concatcp!(
|
||||
#server_fn_path::const_str::replace!(module_path!(), "::", "/"),
|
||||
"/"
|
||||
)
|
||||
}
|
||||
} else {
|
||||
quote! { "" }
|
||||
};
|
||||
|
||||
let enable_hash = option_env!("DISABLE_SERVER_FN_HASH").is_none();
|
||||
let hash = if enable_hash {
|
||||
quote! {
|
||||
#server_fn_path::xxhash_rust::const_xxh64::xxh64(
|
||||
concat!(env!(#key_env_var), ":", file!(), ":", line!(), ":", column!()).as_bytes(),
|
||||
0
|
||||
)
|
||||
}
|
||||
} else {
|
||||
quote! { "" }
|
||||
};
|
||||
|
||||
let path = quote! {
|
||||
if #fn_path.is_empty() {
|
||||
#server_fn_path::const_format::concatcp!(
|
||||
#prefix,
|
||||
"/",
|
||||
#mod_path,
|
||||
#fn_name_as_str,
|
||||
#server_fn_path::xxhash_rust::const_xxh64::xxh64(
|
||||
concat!(env!(#key_env_var), ":", file!(), ":", line!(), ":", column!()).as_bytes(),
|
||||
0
|
||||
)
|
||||
#hash
|
||||
)
|
||||
} else {
|
||||
#server_fn_path::const_format::concatcp!(
|
||||
#prefix,
|
||||
#mod_path,
|
||||
#fn_path
|
||||
)
|
||||
}
|
||||
@@ -730,12 +750,12 @@ fn output_type(return_ty: &Type) -> Result<&GenericArgument> {
|
||||
|
||||
Err(syn::Error::new(
|
||||
return_ty.span(),
|
||||
"server functions should return Result<T, ServerFnError> or Result<T, \
|
||||
ServerFnError<E>>",
|
||||
"server functions should return Result<T, E> where E: \
|
||||
FromServerFnError",
|
||||
))
|
||||
}
|
||||
|
||||
fn err_type(return_ty: &Type) -> Result<Option<&GenericArgument>> {
|
||||
fn err_type(return_ty: &Type) -> Result<Option<&Type>> {
|
||||
if let syn::Type::Path(pat) = &return_ty {
|
||||
if pat.path.segments[0].ident == "Result" {
|
||||
if let PathArguments::AngleBracketed(args) =
|
||||
@@ -746,25 +766,8 @@ fn err_type(return_ty: &Type) -> Result<Option<&GenericArgument>> {
|
||||
return Ok(None);
|
||||
}
|
||||
// Result<T, _>
|
||||
else if let GenericArgument::Type(Type::Path(pat)) =
|
||||
&args.args[1]
|
||||
{
|
||||
if let Some(segment) = pat.path.segments.last() {
|
||||
if segment.ident == "ServerFnError" {
|
||||
let args = &segment.arguments;
|
||||
match args {
|
||||
// Result<T, ServerFnError>
|
||||
PathArguments::None => return Ok(None),
|
||||
// Result<T, ServerFnError<E>>
|
||||
PathArguments::AngleBracketed(args) => {
|
||||
if args.args.len() == 1 {
|
||||
return Ok(Some(&args.args[0]));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if let GenericArgument::Type(ty) = &args.args[1] {
|
||||
return Ok(Some(ty));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -772,8 +775,8 @@ fn err_type(return_ty: &Type) -> Result<Option<&GenericArgument>> {
|
||||
|
||||
Err(syn::Error::new(
|
||||
return_ty.span(),
|
||||
"server functions should return Result<T, ServerFnError> or Result<T, \
|
||||
ServerFnError<E>>",
|
||||
"server functions should return Result<T, E> where E: \
|
||||
FromServerFnError",
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#![allow(unused_mut)]
|
||||
use super::{Attribute, NextAttribute};
|
||||
use dyn_clone::DynClone;
|
||||
use std::{
|
||||
@@ -35,6 +36,10 @@ pub struct AnyAttribute {
|
||||
type_id: TypeId,
|
||||
html_len: usize,
|
||||
value: Box<dyn DynAttr>,
|
||||
|
||||
/// Temporary attribute set during the resolving cycle, to resolve only once.
|
||||
pub(crate) resolved: bool,
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
to_html: fn(
|
||||
Box<dyn DynAttr>,
|
||||
@@ -197,6 +202,7 @@ where
|
||||
type_id: TypeId::of::<T::CloneableOwned>(),
|
||||
html_len: value.html_len(),
|
||||
value,
|
||||
resolved: false,
|
||||
#[cfg(feature = "ssr")]
|
||||
to_html,
|
||||
build,
|
||||
@@ -303,10 +309,13 @@ impl Attribute for AnyAttribute {
|
||||
);
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
async fn resolve(mut self) -> Self::AsyncOutput {
|
||||
#[cfg(feature = "ssr")]
|
||||
{
|
||||
(self.resolve)(self.value).await
|
||||
let res = (self.resolve)(self.value).await;
|
||||
// Used by batch_resolve_items_with_extra_attrs() for optimisations.
|
||||
self.resolved = true;
|
||||
res
|
||||
}
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
panic!(
|
||||
|
||||
@@ -24,7 +24,7 @@ use web_sys::Element;
|
||||
/// let view = element.on(ev::click, move |_| /* ... */);
|
||||
///
|
||||
/// // `element` now contains the actual element
|
||||
/// let element = element.build();
|
||||
/// let element = element.build(None);
|
||||
/// let remove = element.on(ev::blur, move |_| /* ... */);
|
||||
/// ```
|
||||
pub trait ElementExt {
|
||||
|
||||
@@ -6,14 +6,14 @@ use crate::{
|
||||
renderer::{CastFrom, Rndr},
|
||||
ssr::StreamBuilder,
|
||||
view::{
|
||||
add_attr::AddAnyAttr, IntoRender, Mountable, Position, PositionState,
|
||||
Render, RenderHtml, ToTemplate,
|
||||
add_attr::AddAnyAttr, any_view::ExtraAttrsMut, IntoRender, Mountable,
|
||||
Position, PositionState, Render, RenderHtml, ToTemplate,
|
||||
},
|
||||
};
|
||||
use const_str_slice_concat::{
|
||||
const_concat, const_concat_with_prefix, str_from_buffer,
|
||||
};
|
||||
use futures::future::join;
|
||||
use futures::future::join3;
|
||||
use next_tuple::NextTuple;
|
||||
use std::ops::Deref;
|
||||
|
||||
@@ -21,7 +21,10 @@ mod custom;
|
||||
mod element_ext;
|
||||
mod elements;
|
||||
mod inner_html;
|
||||
use super::attribute::{escape_attr, NextAttribute};
|
||||
use super::attribute::{
|
||||
any_attribute::{AnyAttribute, AnyAttributeState},
|
||||
escape_attr, NextAttribute,
|
||||
};
|
||||
pub use custom::*;
|
||||
pub use element_ext::*;
|
||||
pub use elements::*;
|
||||
@@ -183,31 +186,42 @@ where
|
||||
{
|
||||
type State = ElementState<At::State, Ch::State>;
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
let ElementState {
|
||||
attrs, children, ..
|
||||
} = state;
|
||||
self.attributes.rebuild(attrs);
|
||||
if let (Some(extra_attrs), Some(extra_attr_states)) =
|
||||
(extra_attrs, &mut state.extra_attrs)
|
||||
{
|
||||
extra_attrs.rebuild(extra_attr_states);
|
||||
}
|
||||
if let Some(children) = children {
|
||||
self.children.rebuild(children);
|
||||
self.children.rebuild(children, None);
|
||||
}
|
||||
}
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
fn build(self, extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
let el = Rndr::create_element(self.tag.tag(), E::NAMESPACE);
|
||||
|
||||
let attrs = self.attributes.build(&el);
|
||||
let extra_attrs = extra_attrs.map(|attrs| attrs.build(&el));
|
||||
let children = if E::SELF_CLOSING {
|
||||
None
|
||||
} else {
|
||||
let mut children = self.children.build();
|
||||
let mut children = self.children.build(None);
|
||||
children.mount(&el, None);
|
||||
Some(children)
|
||||
};
|
||||
ElementState {
|
||||
el,
|
||||
attrs,
|
||||
children,
|
||||
attrs,
|
||||
extra_attrs,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,6 +233,7 @@ where
|
||||
Ch: RenderHtml + Send,
|
||||
{
|
||||
type AsyncOutput = HtmlElement<E, At::AsyncOutput, Ch::AsyncOutput>;
|
||||
type Owned = HtmlElement<E, At::CloneableOwned, Ch::Owned>;
|
||||
|
||||
const MIN_LENGTH: usize = if E::SELF_CLOSING {
|
||||
3 // < ... />
|
||||
@@ -233,14 +248,22 @@ where
|
||||
+ E::TAG.len()
|
||||
};
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
fn dry_resolve(&mut self, mut extra_attrs: ExtraAttrsMut<'_>) {
|
||||
self.attributes.dry_resolve();
|
||||
self.children.dry_resolve();
|
||||
extra_attrs.iter_mut().for_each(Attribute::dry_resolve);
|
||||
self.children.dry_resolve(ExtraAttrsMut::default());
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
let (attributes, children) =
|
||||
join(self.attributes.resolve(), self.children.resolve()).await;
|
||||
async fn resolve(
|
||||
self,
|
||||
extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
let (attributes, _, children) = join3(
|
||||
self.attributes.resolve(),
|
||||
ExtraAttrsMut::resolve(extra_attrs),
|
||||
self.children.resolve(ExtraAttrsMut::default()),
|
||||
)
|
||||
.await;
|
||||
HtmlElement {
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
@@ -250,7 +273,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn html_len(&self) -> usize {
|
||||
fn html_len(&self, extra_attrs: Option<Vec<&AnyAttribute>>) -> usize {
|
||||
if E::SELF_CLOSING {
|
||||
3 // < ... />
|
||||
+ E::TAG.len()
|
||||
@@ -259,7 +282,10 @@ where
|
||||
2 // < ... >
|
||||
+ E::TAG.len()
|
||||
+ self.attributes.html_len()
|
||||
+ self.children.html_len()
|
||||
+ extra_attrs.map(|attrs| {
|
||||
attrs.into_iter().map(Attribute::html_len).sum::<usize>()
|
||||
}).unwrap_or(0)
|
||||
+ self.children.html_len(None)
|
||||
+ 3 // </ ... >
|
||||
+ E::TAG.len()
|
||||
}
|
||||
@@ -271,12 +297,13 @@ where
|
||||
position: &mut Position,
|
||||
_escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
// opening tag
|
||||
buf.push('<');
|
||||
buf.push_str(self.tag.tag());
|
||||
|
||||
let inner_html = attributes_to_html(self.attributes, buf);
|
||||
let inner_html = attributes_to_html(self.attributes, extra_attrs, buf);
|
||||
|
||||
buf.push('>');
|
||||
|
||||
@@ -291,6 +318,7 @@ where
|
||||
position,
|
||||
E::ESCAPE_CHILDREN,
|
||||
mark_branches,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -308,6 +336,7 @@ where
|
||||
position: &mut Position,
|
||||
_escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -316,7 +345,8 @@ where
|
||||
buf.push('<');
|
||||
buf.push_str(self.tag.tag());
|
||||
|
||||
let inner_html = attributes_to_html(self.attributes, &mut buf);
|
||||
let inner_html =
|
||||
attributes_to_html(self.attributes, extra_attrs, &mut buf);
|
||||
|
||||
buf.push('>');
|
||||
buffer.push_sync(&buf);
|
||||
@@ -332,6 +362,7 @@ where
|
||||
position,
|
||||
E::ESCAPE_CHILDREN,
|
||||
mark_branches,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -349,6 +380,7 @@ where
|
||||
self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
@@ -373,13 +405,15 @@ where
|
||||
});
|
||||
|
||||
let attrs = self.attributes.hydrate::<FROM_SERVER>(&el);
|
||||
let extra_attrs = extra_attrs
|
||||
.map(|attrs| Attribute::hydrate::<FROM_SERVER>(attrs, &el));
|
||||
|
||||
// hydrate children
|
||||
let children = if !Ch::EXISTS || !E::ESCAPE_CHILDREN {
|
||||
None
|
||||
} else {
|
||||
position.set(Position::FirstChild);
|
||||
Some(self.children.hydrate::<FROM_SERVER>(cursor, position))
|
||||
Some(self.children.hydrate::<FROM_SERVER>(cursor, position, None))
|
||||
};
|
||||
|
||||
// go to next sibling
|
||||
@@ -393,14 +427,29 @@ where
|
||||
|
||||
ElementState {
|
||||
el,
|
||||
attrs,
|
||||
children,
|
||||
attrs,
|
||||
extra_attrs,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
HtmlElement {
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
tag: self.tag,
|
||||
attributes: self.attributes.into_cloneable_owned(),
|
||||
children: self.children.into_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders an [`Attribute`] (which can be one or more HTML attributes) into an HTML buffer.
|
||||
pub fn attributes_to_html<At>(attr: At, buf: &mut String) -> String
|
||||
pub fn attributes_to_html<At>(
|
||||
attr: At,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
buf: &mut String,
|
||||
) -> String
|
||||
where
|
||||
At: Attribute,
|
||||
{
|
||||
@@ -419,6 +468,11 @@ where
|
||||
|
||||
// inject regular attributes, and fill class and style
|
||||
attr.to_html(buf, &mut class, &mut style, &mut inner_html);
|
||||
if let Some(extra_attrs) = extra_attrs {
|
||||
for attr in extra_attrs {
|
||||
attr.to_html(buf, &mut class, &mut style, &mut inner_html);
|
||||
}
|
||||
}
|
||||
|
||||
if !class.is_empty() {
|
||||
buf.push(' ');
|
||||
@@ -439,8 +493,10 @@ where
|
||||
/// The retained view state for an HTML element.
|
||||
pub struct ElementState<At, Ch> {
|
||||
pub(crate) el: crate::renderer::types::Element,
|
||||
pub(crate) attrs: At,
|
||||
pub(crate) children: Option<Ch>,
|
||||
|
||||
attrs: At,
|
||||
extra_attrs: Option<Vec<AnyAttributeState>>,
|
||||
}
|
||||
|
||||
impl<At, Ch> Deref for ElementState<At, Ch> {
|
||||
@@ -583,7 +639,7 @@ mod tests {
|
||||
fn mock_dom_creates_element() {
|
||||
let el: HtmlElement<Main, _, _, MockDom> =
|
||||
main().child(p().id("test").lang("en").child("Hello, world!"));
|
||||
let el = el.build();
|
||||
let el = el.build(None);
|
||||
assert_eq!(
|
||||
el.el.to_debug_html(),
|
||||
"<main><p id=\"test\" lang=\"en\">Hello, world!</p></main>"
|
||||
@@ -597,7 +653,7 @@ mod tests {
|
||||
em().child("beautiful"),
|
||||
" world!",
|
||||
)));
|
||||
let el = el.build();
|
||||
let el = el.build(None);
|
||||
assert_eq!(
|
||||
el.el.to_debug_html(),
|
||||
"<main><p>Hello, <em>beautiful</em> world!</p></main>"
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use super::attribute::Attribute;
|
||||
use super::attribute::{any_attribute::AnyAttribute, Attribute};
|
||||
use crate::{
|
||||
hydration::Cursor,
|
||||
prelude::{Render, RenderHtml},
|
||||
ssr::StreamBuilder,
|
||||
view::{add_attr::AddAnyAttr, Position, PositionState},
|
||||
view::{
|
||||
add_attr::AddAnyAttr, any_view::ExtraAttrsMut, Position, PositionState,
|
||||
},
|
||||
};
|
||||
|
||||
/// An island of interactivity in an otherwise-inert HTML document.
|
||||
@@ -59,12 +61,16 @@ where
|
||||
{
|
||||
type State = View::State;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
self.view.build()
|
||||
fn build(self, extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
self.view.build(extra_attrs)
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
self.view.rebuild(state);
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
self.view.rebuild(state, extra_attrs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +106,7 @@ where
|
||||
View: RenderHtml,
|
||||
{
|
||||
type AsyncOutput = Island<View::AsyncOutput>;
|
||||
type Owned = Island<View::Owned>;
|
||||
|
||||
const MIN_LENGTH: usize = ISLAND_TAG.len() * 2
|
||||
+ "<>".len()
|
||||
@@ -107,11 +114,14 @@ where
|
||||
+ "data-component".len()
|
||||
+ View::MIN_LENGTH;
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
self.view.dry_resolve()
|
||||
fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>) {
|
||||
self.view.dry_resolve(extra_attrs)
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
async fn resolve(
|
||||
self,
|
||||
extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
let Island {
|
||||
component,
|
||||
props_json,
|
||||
@@ -120,7 +130,7 @@ where
|
||||
Island {
|
||||
component,
|
||||
props_json,
|
||||
view: view.resolve().await,
|
||||
view: view.resolve(extra_attrs).await,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,10 +140,16 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
Self::open_tag(self.component, &self.props_json, buf);
|
||||
self.view
|
||||
.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
self.view.to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
Self::close_tag(buf);
|
||||
}
|
||||
|
||||
@@ -143,6 +159,7 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -157,6 +174,7 @@ where
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
|
||||
// and insert the closing tag synchronously
|
||||
@@ -169,6 +187,7 @@ where
|
||||
self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
if position.get() == Position::FirstChild {
|
||||
cursor.child();
|
||||
@@ -177,7 +196,16 @@ where
|
||||
}
|
||||
position.set(Position::FirstChild);
|
||||
|
||||
self.view.hydrate::<FROM_SERVER>(cursor, position)
|
||||
self.view
|
||||
.hydrate::<FROM_SERVER>(cursor, position, extra_attrs)
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
Island {
|
||||
component: self.component,
|
||||
props_json: self.props_json,
|
||||
view: self.view.into_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,9 +255,14 @@ where
|
||||
{
|
||||
type State = ();
|
||||
|
||||
fn build(self) -> Self::State {}
|
||||
fn build(self, _extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {}
|
||||
|
||||
fn rebuild(self, _state: &mut Self::State) {}
|
||||
fn rebuild(
|
||||
self,
|
||||
_state: &mut Self::State,
|
||||
_extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl<View> AddAnyAttr for IslandChildren<View>
|
||||
@@ -259,20 +292,24 @@ where
|
||||
View: RenderHtml,
|
||||
{
|
||||
type AsyncOutput = IslandChildren<View::AsyncOutput>;
|
||||
type Owned = IslandChildren<View::Owned>;
|
||||
|
||||
const MIN_LENGTH: usize = ISLAND_CHILDREN_TAG.len() * 2
|
||||
+ "<>".len()
|
||||
+ "</>".len()
|
||||
+ View::MIN_LENGTH;
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
self.view.dry_resolve()
|
||||
fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>) {
|
||||
self.view.dry_resolve(extra_attrs)
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
async fn resolve(
|
||||
self,
|
||||
extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
let IslandChildren { view, on_hydrate } = self;
|
||||
IslandChildren {
|
||||
view: view.resolve().await,
|
||||
view: view.resolve(extra_attrs).await,
|
||||
on_hydrate,
|
||||
}
|
||||
}
|
||||
@@ -283,10 +320,16 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
Self::open_tag(buf);
|
||||
self.view
|
||||
.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
self.view.to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
Self::close_tag(buf);
|
||||
}
|
||||
|
||||
@@ -296,6 +339,7 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -310,6 +354,7 @@ where
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
|
||||
// and insert the closing tag synchronously
|
||||
@@ -322,6 +367,7 @@ where
|
||||
self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
_extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
// island children aren't hydrated
|
||||
// we update the walk to pass over them
|
||||
@@ -356,4 +402,11 @@ where
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
IslandChildren {
|
||||
view: self.view.into_owned(),
|
||||
on_hydrate: self.on_hydrate,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,11 @@ use crate::{
|
||||
dom::{Element, Node},
|
||||
CastFrom, Rndr,
|
||||
},
|
||||
view::{Position, PositionState, Render, RenderHtml},
|
||||
view::{
|
||||
any_view::ExtraAttrsMut, Position, PositionState, Render, RenderHtml,
|
||||
},
|
||||
};
|
||||
use attribute::any_attribute::AnyAttribute;
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Types for HTML attributes.
|
||||
@@ -43,21 +46,30 @@ pub fn doctype(value: &'static str) -> Doctype {
|
||||
impl Render for Doctype {
|
||||
type State = ();
|
||||
|
||||
fn build(self) -> Self::State {}
|
||||
fn build(self, _extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {}
|
||||
|
||||
fn rebuild(self, _state: &mut Self::State) {}
|
||||
fn rebuild(
|
||||
self,
|
||||
_state: &mut Self::State,
|
||||
_extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
no_attrs!(Doctype);
|
||||
|
||||
impl RenderHtml for Doctype {
|
||||
type AsyncOutput = Self;
|
||||
type Owned = Self;
|
||||
|
||||
const MIN_LENGTH: usize = "<!DOCTYPE html>".len();
|
||||
|
||||
fn dry_resolve(&mut self) {}
|
||||
fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
async fn resolve(
|
||||
self,
|
||||
_extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
self
|
||||
}
|
||||
|
||||
@@ -67,6 +79,7 @@ impl RenderHtml for Doctype {
|
||||
_position: &mut Position,
|
||||
_escape: bool,
|
||||
_mark_branches: bool,
|
||||
_extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
buf.push_str("<!DOCTYPE ");
|
||||
buf.push_str(self.value);
|
||||
@@ -77,8 +90,13 @@ impl RenderHtml for Doctype {
|
||||
self,
|
||||
_cursor: &Cursor,
|
||||
_position: &PositionState,
|
||||
_extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// An element that contains no interactivity, and whose contents can be known at compile time.
|
||||
@@ -113,12 +131,16 @@ impl Mountable for InertElementState {
|
||||
impl Render for InertElement {
|
||||
type State = InertElementState;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
fn build(self, _extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
let el = Rndr::create_element_from_html(&self.html);
|
||||
InertElementState(self.html, el)
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
_extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
let InertElementState(prev, el) = state;
|
||||
if &self.html != prev {
|
||||
let mut new_el = Rndr::create_element_from_html(&self.html);
|
||||
@@ -149,16 +171,17 @@ impl AddAnyAttr for InertElement {
|
||||
|
||||
impl RenderHtml for InertElement {
|
||||
type AsyncOutput = Self;
|
||||
type Owned = Self;
|
||||
|
||||
const MIN_LENGTH: usize = 0;
|
||||
|
||||
fn html_len(&self) -> usize {
|
||||
fn html_len(&self, _extra_attrs: Option<Vec<&AnyAttribute>>) -> usize {
|
||||
self.html.len()
|
||||
}
|
||||
|
||||
fn dry_resolve(&mut self) {}
|
||||
fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {}
|
||||
|
||||
async fn resolve(self) -> Self {
|
||||
async fn resolve(self, _extra_attrs: ExtraAttrsMut<'_>) -> Self {
|
||||
self
|
||||
}
|
||||
|
||||
@@ -168,6 +191,7 @@ impl RenderHtml for InertElement {
|
||||
position: &mut Position,
|
||||
_escape: bool,
|
||||
_mark_branches: bool,
|
||||
_extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
buf.push_str(&self.html);
|
||||
*position = Position::NextChild;
|
||||
@@ -177,6 +201,7 @@ impl RenderHtml for InertElement {
|
||||
self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
_extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
let curr_position = position.get();
|
||||
if curr_position == Position::FirstChild {
|
||||
@@ -189,4 +214,8 @@ impl RenderHtml for InertElement {
|
||||
position.set(Position::NextChild);
|
||||
InertElementState(self.html, el)
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
use crate::{
|
||||
html::{attribute::AttributeValue, class::IntoClass, style::IntoStyle},
|
||||
html::{
|
||||
attribute::{any_attribute::AnyAttribute, AttributeValue},
|
||||
class::IntoClass,
|
||||
style::IntoStyle,
|
||||
},
|
||||
hydration::Cursor,
|
||||
no_attrs,
|
||||
prelude::{Mountable, Render, RenderHtml},
|
||||
renderer::Rndr,
|
||||
view::{strings::StrState, Position, PositionState, ToTemplate},
|
||||
view::{
|
||||
any_view::ExtraAttrsMut, strings::StrState, Position, PositionState,
|
||||
ToTemplate,
|
||||
},
|
||||
};
|
||||
use oco_ref::Oco;
|
||||
|
||||
@@ -17,12 +24,16 @@ pub struct OcoStrState {
|
||||
impl Render for Oco<'static, str> {
|
||||
type State = OcoStrState;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
fn build(self, _extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
let node = Rndr::create_text_node(&self);
|
||||
OcoStrState { node, str: self }
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
_extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
let OcoStrState { node, str } = state;
|
||||
if &self != str {
|
||||
Rndr::set_text(node, &self);
|
||||
@@ -35,12 +46,16 @@ no_attrs!(Oco<'static, str>);
|
||||
|
||||
impl RenderHtml for Oco<'static, str> {
|
||||
type AsyncOutput = Self;
|
||||
type Owned = Self;
|
||||
|
||||
const MIN_LENGTH: usize = 0;
|
||||
|
||||
fn dry_resolve(&mut self) {}
|
||||
fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
async fn resolve(
|
||||
self,
|
||||
_extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
self
|
||||
}
|
||||
|
||||
@@ -50,6 +65,7 @@ impl RenderHtml for Oco<'static, str> {
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
<&str as RenderHtml>::to_html_with_buf(
|
||||
&self,
|
||||
@@ -57,6 +73,7 @@ impl RenderHtml for Oco<'static, str> {
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -64,13 +81,21 @@ impl RenderHtml for Oco<'static, str> {
|
||||
self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
let this: &str = self.as_ref();
|
||||
let StrState { node, .. } = <&str as RenderHtml>::hydrate::<FROM_SERVER>(
|
||||
this, cursor, position,
|
||||
this,
|
||||
cursor,
|
||||
position,
|
||||
extra_attrs,
|
||||
);
|
||||
OcoStrState { node, str: self }
|
||||
}
|
||||
|
||||
fn into_owned(self) -> <Self as RenderHtml>::Owned {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTemplate for Oco<'static, str> {
|
||||
|
||||
@@ -352,7 +352,7 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
fn rebuild(self, state: &mut Self::State, _extra_attrs: Option<Vec<AnyAttribute>>) {
|
||||
let (names, mut f) = self;
|
||||
let prev_value = state.take_value();
|
||||
|
||||
@@ -433,7 +433,7 @@ where
|
||||
<String as IntoClass>::build(self.deref().to_owned(), el)
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
fn rebuild(self, state: &mut Self::State, _extra_attrs: Option<Vec<AnyAttribute>>) {
|
||||
<String as IntoClass>::rebuild(self.deref().to_owned(), state)
|
||||
}
|
||||
|
||||
@@ -447,7 +447,7 @@ where
|
||||
|
||||
fn dry_resolve(&mut self) {}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
async fn resolve(self, _extra_attrs: Option<Vec<AnyAttribute>>) -> Self::AsyncOutput {
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -489,7 +489,7 @@ where
|
||||
)
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
fn rebuild(self, state: &mut Self::State, _extra_attrs: Option<Vec<AnyAttribute>>) {
|
||||
<(&'static str, bool) as IntoClass>::rebuild(
|
||||
(self.0, *self.1.deref()),
|
||||
state,
|
||||
@@ -506,7 +506,7 @@ where
|
||||
|
||||
fn dry_resolve(&mut self) {}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
async fn resolve(self, _extra_attrs: Option<Vec<AnyAttribute>>) -> Self::AsyncOutput {
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -901,7 +901,7 @@ where
|
||||
state
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
fn rebuild(self, state: &mut Self::State, _extra_attrs: Option<Vec<AnyAttribute>>) {
|
||||
reactive_graph::spawn_local_scoped({
|
||||
let state = Rc::clone(state);
|
||||
async move {
|
||||
@@ -925,7 +925,7 @@ where
|
||||
|
||||
fn dry_resolve(&mut self) {}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
async fn resolve(self, _extra_attrs: Option<Vec<AnyAttribute>>) -> Self::AsyncOutput {
|
||||
self.inner.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use crate::{
|
||||
html::attribute::{Attribute, AttributeValue},
|
||||
html::attribute::{any_attribute::AnyAttribute, Attribute, AttributeValue},
|
||||
hydration::Cursor,
|
||||
renderer::Rndr,
|
||||
ssr::StreamBuilder,
|
||||
view::{
|
||||
add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
|
||||
RenderHtml, ToTemplate,
|
||||
add_attr::AddAnyAttr, any_view::ExtraAttrsMut, Mountable, Position,
|
||||
PositionState, Render, RenderHtml, ToTemplate,
|
||||
},
|
||||
};
|
||||
use reactive_graph::effect::RenderEffect;
|
||||
@@ -57,7 +57,7 @@ where
|
||||
type State = RenderEffectState<V::State>;
|
||||
|
||||
#[track_caller]
|
||||
fn build(mut self) -> Self::State {
|
||||
fn build(mut self, extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
let hook = throw_error::get_error_hook();
|
||||
RenderEffect::new(move |prev| {
|
||||
let _guard = hook
|
||||
@@ -65,18 +65,22 @@ where
|
||||
.map(|h| throw_error::set_error_hook(Arc::clone(h)));
|
||||
let value = self.invoke();
|
||||
if let Some(mut state) = prev {
|
||||
value.rebuild(&mut state);
|
||||
value.rebuild(&mut state, extra_attrs.clone());
|
||||
state
|
||||
} else {
|
||||
value.build()
|
||||
value.build(extra_attrs.clone())
|
||||
}
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
let new = self.build();
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
let new = self.build(extra_attrs);
|
||||
let mut old = std::mem::replace(state, new);
|
||||
old.insert_before_this(state);
|
||||
old.unmount();
|
||||
@@ -128,18 +132,22 @@ where
|
||||
V::State: 'static,
|
||||
{
|
||||
type AsyncOutput = V::AsyncOutput;
|
||||
type Owned = F;
|
||||
|
||||
const MIN_LENGTH: usize = 0;
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
self.invoke().dry_resolve();
|
||||
fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>) {
|
||||
self.invoke().dry_resolve(extra_attrs);
|
||||
}
|
||||
|
||||
async fn resolve(mut self) -> Self::AsyncOutput {
|
||||
self.invoke().resolve().await
|
||||
async fn resolve(
|
||||
mut self,
|
||||
extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
self.invoke().resolve(extra_attrs).await
|
||||
}
|
||||
|
||||
fn html_len(&self) -> usize {
|
||||
fn html_len(&self, _extra_attrs: Option<Vec<&AnyAttribute>>) -> usize {
|
||||
V::MIN_LENGTH
|
||||
}
|
||||
|
||||
@@ -149,9 +157,16 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
let value = self.invoke();
|
||||
value.to_html_with_buf(buf, position, escape, mark_branches)
|
||||
value.to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
)
|
||||
}
|
||||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
@@ -160,6 +175,7 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -169,6 +185,7 @@ where
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -176,6 +193,7 @@ where
|
||||
mut self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
let cursor = cursor.clone();
|
||||
let position = position.clone();
|
||||
@@ -186,14 +204,22 @@ where
|
||||
.map(|h| throw_error::set_error_hook(Arc::clone(h)));
|
||||
let value = self.invoke();
|
||||
if let Some(mut state) = prev {
|
||||
value.rebuild(&mut state);
|
||||
value.rebuild(&mut state, extra_attrs.clone());
|
||||
state
|
||||
} else {
|
||||
value.hydrate::<FROM_SERVER>(&cursor, &position)
|
||||
value.hydrate::<FROM_SERVER>(
|
||||
&cursor,
|
||||
&position,
|
||||
extra_attrs.clone(),
|
||||
)
|
||||
}
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, V> AddAnyAttr for F
|
||||
@@ -507,12 +533,14 @@ where
|
||||
mod stable {
|
||||
use super::RenderEffectState;
|
||||
use crate::{
|
||||
html::attribute::{Attribute, AttributeValue},
|
||||
html::attribute::{
|
||||
any_attribute::AnyAttribute, Attribute, AttributeValue,
|
||||
},
|
||||
hydration::Cursor,
|
||||
ssr::StreamBuilder,
|
||||
view::{
|
||||
add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
|
||||
RenderHtml,
|
||||
add_attr::AddAnyAttr, any_view::ExtraAttrsMut, Mountable, Position,
|
||||
PositionState, Render, RenderHtml,
|
||||
},
|
||||
};
|
||||
#[allow(deprecated)]
|
||||
@@ -537,13 +565,20 @@ mod stable {
|
||||
type State = RenderEffectState<V::State>;
|
||||
|
||||
#[track_caller]
|
||||
fn build(self) -> Self::State {
|
||||
(move || self.get()).build()
|
||||
fn build(
|
||||
self,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
(move || self.get()).build(extra_attrs)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
let new = self.build();
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
let new = self.build(extra_attrs);
|
||||
let mut old = std::mem::replace(state, new);
|
||||
old.insert_before_this(state);
|
||||
old.unmount();
|
||||
@@ -576,20 +611,27 @@ mod stable {
|
||||
V::State: 'static,
|
||||
{
|
||||
type AsyncOutput = Self;
|
||||
type Owned = Self;
|
||||
|
||||
const MIN_LENGTH: usize = 0;
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {
|
||||
if $dry_resolve {
|
||||
_ = self.get();
|
||||
}
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
async fn resolve(
|
||||
self,
|
||||
_extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
self
|
||||
}
|
||||
|
||||
fn html_len(&self) -> usize {
|
||||
fn html_len(
|
||||
&self,
|
||||
_extra_attrs: Option<Vec<&AnyAttribute>>,
|
||||
) -> usize {
|
||||
V::MIN_LENGTH
|
||||
}
|
||||
|
||||
@@ -599,9 +641,16 @@ mod stable {
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
let value = self.get();
|
||||
value.to_html_with_buf(buf, position, escape, mark_branches)
|
||||
value.to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
)
|
||||
}
|
||||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
@@ -610,6 +659,7 @@ mod stable {
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -619,6 +669,7 @@ mod stable {
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -626,9 +677,17 @@ mod stable {
|
||||
self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
(move || self.get())
|
||||
.hydrate::<FROM_SERVER>(cursor, position)
|
||||
(move || self.get()).hydrate::<FROM_SERVER>(
|
||||
cursor,
|
||||
position,
|
||||
extra_attrs,
|
||||
)
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -705,13 +764,20 @@ mod stable {
|
||||
type State = RenderEffectState<V::State>;
|
||||
|
||||
#[track_caller]
|
||||
fn build(self) -> Self::State {
|
||||
(move || self.get()).build()
|
||||
fn build(
|
||||
self,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
(move || self.get()).build(extra_attrs)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
let new = self.build();
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
let new = self.build(extra_attrs);
|
||||
let mut old = std::mem::replace(state, new);
|
||||
old.insert_before_this(state);
|
||||
old.unmount();
|
||||
@@ -750,20 +816,27 @@ mod stable {
|
||||
V::State: 'static,
|
||||
{
|
||||
type AsyncOutput = Self;
|
||||
type Owned = Self;
|
||||
|
||||
const MIN_LENGTH: usize = 0;
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {
|
||||
if $dry_resolve {
|
||||
_ = self.get();
|
||||
}
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
async fn resolve(
|
||||
self,
|
||||
_extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
self
|
||||
}
|
||||
|
||||
fn html_len(&self) -> usize {
|
||||
fn html_len(
|
||||
&self,
|
||||
_extra_attrs: Option<Vec<&AnyAttribute>>,
|
||||
) -> usize {
|
||||
V::MIN_LENGTH
|
||||
}
|
||||
|
||||
@@ -773,9 +846,16 @@ mod stable {
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
let value = self.get();
|
||||
value.to_html_with_buf(buf, position, escape, mark_branches)
|
||||
value.to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
)
|
||||
}
|
||||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
@@ -784,6 +864,7 @@ mod stable {
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -793,6 +874,7 @@ mod stable {
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -800,9 +882,17 @@ mod stable {
|
||||
self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
(move || self.get())
|
||||
.hydrate::<FROM_SERVER>(cursor, position)
|
||||
(move || self.get()).hydrate::<FROM_SERVER>(
|
||||
cursor,
|
||||
position,
|
||||
extra_attrs,
|
||||
)
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -895,7 +985,7 @@ mod tests {
|
||||
let count = RwSignal::new(0);
|
||||
let app: HtmlElement<_, _, _, MockDom> =
|
||||
button((), move || count.get().to_string());
|
||||
let el = app.build();
|
||||
let el = app.build(None);
|
||||
assert_eq!(el.el.to_debug_html(), "<button>0</button>");
|
||||
rt.dispose();
|
||||
}
|
||||
@@ -906,7 +996,7 @@ mod tests {
|
||||
let count = RwSignal::new(0);
|
||||
let app: HtmlElement<_, _, _, MockDom> =
|
||||
button((), move || count.get().to_string());
|
||||
let el = app.build();
|
||||
let el = app.build(None);
|
||||
assert_eq!(el.el.to_debug_html(), "<button>0</button>");
|
||||
count.set(1);
|
||||
assert_eq!(el.el.to_debug_html(), "<button>1</button>");
|
||||
@@ -924,7 +1014,7 @@ mod tests {
|
||||
("Hello, my ", move || count.get().to_string(), " friends."),
|
||||
),
|
||||
);
|
||||
let el = app.build();
|
||||
let el = app.build(None);
|
||||
assert_eq!(
|
||||
el.el.to_debug_html(),
|
||||
"<main><button>Hello, my 0 friends.</button></main>"
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
use crate::{
|
||||
html::attribute::Attribute,
|
||||
html::attribute::{any_attribute::AnyAttribute, Attribute},
|
||||
hydration::Cursor,
|
||||
prelude::Mountable,
|
||||
ssr::StreamBuilder,
|
||||
view::{add_attr::AddAnyAttr, Position, PositionState, Render, RenderHtml},
|
||||
view::{
|
||||
add_attr::AddAnyAttr, any_view::ExtraAttrsMut, Position, PositionState,
|
||||
Render, RenderHtml,
|
||||
},
|
||||
};
|
||||
use reactive_graph::{computed::ScopedFuture, owner::Owner};
|
||||
|
||||
@@ -53,14 +56,18 @@ where
|
||||
{
|
||||
type State = OwnedViewState<T::State>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
let state = self.owner.with(|| self.view.build());
|
||||
fn build(self, extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
let state = self.owner.with(|| self.view.build(extra_attrs));
|
||||
OwnedViewState::new(state, self.owner)
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
let OwnedView { owner, view, .. } = self;
|
||||
owner.with(|| view.rebuild(&mut state.state));
|
||||
owner.with(|| view.rebuild(&mut state.state, extra_attrs));
|
||||
state.owner = owner;
|
||||
}
|
||||
}
|
||||
@@ -92,6 +99,7 @@ where
|
||||
{
|
||||
// TODO
|
||||
type AsyncOutput = OwnedView<T::AsyncOutput>;
|
||||
type Owned = OwnedView<T::Owned>;
|
||||
|
||||
const MIN_LENGTH: usize = T::MIN_LENGTH;
|
||||
|
||||
@@ -101,10 +109,16 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
self.owner.with(|| {
|
||||
self.view
|
||||
.to_html_with_buf(buf, position, escape, mark_branches)
|
||||
self.view.to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -114,6 +128,7 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -123,6 +138,7 @@ where
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
)
|
||||
});
|
||||
|
||||
@@ -137,23 +153,39 @@ where
|
||||
self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
let state = self
|
||||
.owner
|
||||
.with(|| self.view.hydrate::<FROM_SERVER>(cursor, position));
|
||||
let state = self.owner.with(|| {
|
||||
self.view
|
||||
.hydrate::<FROM_SERVER>(cursor, position, extra_attrs)
|
||||
});
|
||||
OwnedViewState::new(state, self.owner)
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
async fn resolve(
|
||||
self,
|
||||
extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
let OwnedView { owner, view } = self;
|
||||
let view = owner
|
||||
.with(|| ScopedFuture::new(async move { view.resolve().await }))
|
||||
.with(|| {
|
||||
ScopedFuture::new(
|
||||
async move { view.resolve(extra_attrs).await },
|
||||
)
|
||||
})
|
||||
.await;
|
||||
OwnedView { owner, view }
|
||||
}
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
self.owner.with(|| self.view.dry_resolve());
|
||||
fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>) {
|
||||
self.owner.with(|| self.view.dry_resolve(extra_attrs));
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
OwnedView {
|
||||
owner: self.owner,
|
||||
view: self.view.into_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::{
|
||||
html::attribute::Attribute,
|
||||
html::attribute::{any_attribute::AnyAttribute, Attribute},
|
||||
hydration::Cursor,
|
||||
ssr::StreamBuilder,
|
||||
view::{
|
||||
add_attr::AddAnyAttr, iterators::OptionState, Mountable, Position,
|
||||
PositionState, Render, RenderHtml,
|
||||
add_attr::AddAnyAttr, any_view::ExtraAttrsMut, iterators::OptionState,
|
||||
Mountable, Position, PositionState, Render, RenderHtml,
|
||||
},
|
||||
};
|
||||
use any_spawner::Executor;
|
||||
@@ -27,7 +27,7 @@ use reactive_graph::{
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
fmt::Debug,
|
||||
future::Future,
|
||||
future::{Future, IntoFuture},
|
||||
mem,
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
@@ -115,11 +115,15 @@ impl ToAnySubscriber for SuspendSubscriber {
|
||||
|
||||
impl<T> Suspend<T> {
|
||||
/// Creates a new suspended view.
|
||||
pub fn new(fut: impl Future<Output = T> + Send + 'static) -> Self {
|
||||
pub fn new<Fut>(fut: Fut) -> Self
|
||||
where
|
||||
Fut: IntoFuture<Output = T>,
|
||||
Fut::IntoFuture: Send + 'static,
|
||||
{
|
||||
let subscriber = SuspendSubscriber::new();
|
||||
let any_subscriber = subscriber.to_any_subscriber();
|
||||
let inner =
|
||||
any_subscriber.with_observer(|| Box::pin(ScopedFuture::new(fut)));
|
||||
let inner = any_subscriber
|
||||
.with_observer(|| Box::pin(ScopedFuture::new(fut.into_future())));
|
||||
Self { subscriber, inner }
|
||||
}
|
||||
}
|
||||
@@ -165,7 +169,7 @@ where
|
||||
{
|
||||
type State = SuspendState<T>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
fn build(self, extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
let Self { subscriber, inner } = self;
|
||||
|
||||
// create a Future that will be aborted on on_cleanup
|
||||
@@ -180,7 +184,7 @@ where
|
||||
// otherwise, start with the fallback
|
||||
let initial = fut.as_mut().now_or_never().and_then(Result::ok);
|
||||
let initially_pending = initial.is_none();
|
||||
let inner = Rc::new(RefCell::new(initial.build()));
|
||||
let inner = Rc::new(RefCell::new(initial.build(extra_attrs.clone())));
|
||||
|
||||
// get a unique ID if there's a SuspenseContext
|
||||
let id = use_context::<SuspenseContext>().map(|sc| sc.task_id());
|
||||
@@ -201,7 +205,8 @@ where
|
||||
drop(id);
|
||||
|
||||
if let Ok(value) = value {
|
||||
Some(value).rebuild(&mut *state.borrow_mut());
|
||||
Some(value)
|
||||
.rebuild(&mut *state.borrow_mut(), extra_attrs);
|
||||
}
|
||||
|
||||
subscriber.forward();
|
||||
@@ -214,7 +219,11 @@ where
|
||||
SuspendState { inner }
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
let Self { subscriber, inner } = self;
|
||||
|
||||
// create a Future that will be aborted on on_cleanup
|
||||
@@ -244,7 +253,7 @@ where
|
||||
// has no parent
|
||||
Executor::tick().await;
|
||||
if let Ok(value) = value {
|
||||
Some(value).rebuild(&mut *state.borrow_mut());
|
||||
Some(value).rebuild(&mut *state.borrow_mut(), extra_attrs);
|
||||
}
|
||||
|
||||
subscriber.forward();
|
||||
@@ -280,6 +289,7 @@ where
|
||||
T: RenderHtml + Sized + 'static,
|
||||
{
|
||||
type AsyncOutput = Option<T>;
|
||||
type Owned = Self;
|
||||
|
||||
const MIN_LENGTH: usize = T::MIN_LENGTH;
|
||||
|
||||
@@ -289,12 +299,19 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
// TODO wrap this with a Suspense as needed
|
||||
// currently this is just used for Routes, which creates a Suspend but never actually needs
|
||||
// it (because we don't lazy-load routes on the server)
|
||||
if let Some(inner) = self.inner.now_or_never() {
|
||||
inner.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
inner.to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,6 +321,7 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -314,6 +332,7 @@ where
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
),
|
||||
None => {
|
||||
if use_context::<SuspenseContext>().is_none() {
|
||||
@@ -338,6 +357,7 @@ where
|
||||
(),
|
||||
&mut fallback_position,
|
||||
mark_branches,
|
||||
extra_attrs.clone(),
|
||||
);
|
||||
|
||||
// TODO in 0.8: this should include a nonce
|
||||
@@ -349,6 +369,7 @@ where
|
||||
fut,
|
||||
position,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
} else {
|
||||
buf.push_async({
|
||||
@@ -361,6 +382,7 @@ where
|
||||
&mut position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
builder.finish().take_chunks()
|
||||
}
|
||||
@@ -377,6 +399,7 @@ where
|
||||
self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
let Self { subscriber, inner } = self;
|
||||
|
||||
@@ -392,9 +415,11 @@ where
|
||||
// otherwise, start with the fallback
|
||||
let initial = fut.as_mut().now_or_never().and_then(Result::ok);
|
||||
let initially_pending = initial.is_none();
|
||||
let inner = Rc::new(RefCell::new(
|
||||
initial.hydrate::<FROM_SERVER>(cursor, position),
|
||||
));
|
||||
let inner = Rc::new(RefCell::new(initial.hydrate::<FROM_SERVER>(
|
||||
cursor,
|
||||
position,
|
||||
extra_attrs.clone(),
|
||||
)));
|
||||
|
||||
// get a unique ID if there's a SuspenseContext
|
||||
let id = use_context::<SuspenseContext>().map(|sc| sc.task_id());
|
||||
@@ -415,7 +440,8 @@ where
|
||||
drop(id);
|
||||
|
||||
if let Ok(value) = value {
|
||||
Some(value).rebuild(&mut *state.borrow_mut());
|
||||
Some(value)
|
||||
.rebuild(&mut *state.borrow_mut(), extra_attrs);
|
||||
}
|
||||
|
||||
subscriber.forward();
|
||||
@@ -428,11 +454,14 @@ where
|
||||
SuspendState { inner }
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
async fn resolve(
|
||||
self,
|
||||
_extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
Some(self.inner.await)
|
||||
}
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {
|
||||
// this is a little crazy, but if a Suspend is immediately available, we end up
|
||||
// with a situation where polling it multiple times (here in dry_resolve and then in
|
||||
// resolve) causes a runtime panic
|
||||
@@ -452,4 +481,8 @@ where
|
||||
as Pin<Box<dyn Future<Output = T> + Send>>;
|
||||
}
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use crate::view::{Position, RenderHtml};
|
||||
use crate::{
|
||||
html::attribute::any_attribute::AnyAttribute,
|
||||
view::{Position, RenderHtml},
|
||||
};
|
||||
use futures::Stream;
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
@@ -103,6 +106,7 @@ impl StreamBuilder {
|
||||
fallback: View,
|
||||
position: &mut Position,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
View: RenderHtml,
|
||||
{
|
||||
@@ -112,6 +116,7 @@ impl StreamBuilder {
|
||||
position,
|
||||
true,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
self.write_chunk_marker(false);
|
||||
*position = Position::NextChild;
|
||||
@@ -160,6 +165,7 @@ impl StreamBuilder {
|
||||
view: impl Future<Output = Option<View>> + Send + 'static,
|
||||
position: &mut Position,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
View: RenderHtml,
|
||||
{
|
||||
@@ -168,6 +174,7 @@ impl StreamBuilder {
|
||||
position,
|
||||
mark_branches,
|
||||
None,
|
||||
extra_attrs,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -178,6 +185,7 @@ impl StreamBuilder {
|
||||
position: &mut Position,
|
||||
mark_branches: bool,
|
||||
nonce: Option<Arc<str>>,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
View: RenderHtml,
|
||||
{
|
||||
@@ -207,6 +215,7 @@ impl StreamBuilder {
|
||||
&mut position,
|
||||
true,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
}
|
||||
let chunks = subbuilder.finish().take_chunks();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(unused_mut)]
|
||||
#![allow(clippy::type_complexity)]
|
||||
#[cfg(feature = "ssr")]
|
||||
use super::MarkBranch;
|
||||
use super::{
|
||||
@@ -5,7 +7,12 @@ use super::{
|
||||
RenderHtml,
|
||||
};
|
||||
use crate::{
|
||||
html::attribute::Attribute, hydration::Cursor, ssr::StreamBuilder,
|
||||
html::attribute::{
|
||||
any_attribute::{AnyAttribute, IntoAnyAttribute},
|
||||
Attribute,
|
||||
},
|
||||
hydration::Cursor,
|
||||
ssr::StreamBuilder,
|
||||
};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
@@ -27,38 +34,60 @@ use std::{future::Future, pin::Pin};
|
||||
pub struct AnyView {
|
||||
type_id: TypeId,
|
||||
value: Box<dyn Any + Send>,
|
||||
build: fn(Box<dyn Any>) -> AnyViewState,
|
||||
rebuild: fn(TypeId, Box<dyn Any>, &mut AnyViewState),
|
||||
// Without erasure, tuples of attrs created by default cause too much type explosion to enable.
|
||||
#[cfg(erase_components)]
|
||||
add_any_attr: fn(
|
||||
Box<dyn Any>,
|
||||
crate::html::attribute::any_attribute::AnyAttribute,
|
||||
) -> AnyView,
|
||||
extra_attrs: Vec<AnyAttribute>,
|
||||
build: fn(Box<dyn Any>, Option<Vec<AnyAttribute>>) -> AnyViewState,
|
||||
rebuild: fn(Box<dyn Any>, &mut AnyViewState, Option<Vec<AnyAttribute>>),
|
||||
// The fields below are cfg-gated so they will not be included in WASM bundles if not needed.
|
||||
// Ordinarily, the compiler can simply omit this dead code because the methods are not called.
|
||||
// With this type-erased wrapper, however, the compiler is not *always* able to correctly
|
||||
// eliminate that code.
|
||||
#[cfg(feature = "ssr")]
|
||||
html_len: usize,
|
||||
html_len: fn(&Box<dyn Any + Send>, Option<Vec<&AnyAttribute>>) -> usize,
|
||||
#[cfg(feature = "ssr")]
|
||||
to_html: fn(Box<dyn Any>, &mut String, &mut Position, bool, bool),
|
||||
to_html: fn(
|
||||
Box<dyn Any>,
|
||||
&mut String,
|
||||
&mut Position,
|
||||
bool,
|
||||
bool,
|
||||
Option<Vec<AnyAttribute>>,
|
||||
),
|
||||
#[cfg(feature = "ssr")]
|
||||
to_html_async:
|
||||
fn(Box<dyn Any>, &mut StreamBuilder, &mut Position, bool, bool),
|
||||
to_html_async: fn(
|
||||
Box<dyn Any>,
|
||||
&mut StreamBuilder,
|
||||
&mut Position,
|
||||
bool,
|
||||
bool,
|
||||
Option<Vec<AnyAttribute>>,
|
||||
),
|
||||
#[cfg(feature = "ssr")]
|
||||
to_html_async_ooo:
|
||||
fn(Box<dyn Any>, &mut StreamBuilder, &mut Position, bool, bool),
|
||||
to_html_async_ooo: fn(
|
||||
Box<dyn Any>,
|
||||
&mut StreamBuilder,
|
||||
&mut Position,
|
||||
bool,
|
||||
bool,
|
||||
Option<Vec<AnyAttribute>>,
|
||||
),
|
||||
#[cfg(feature = "ssr")]
|
||||
#[allow(clippy::type_complexity)]
|
||||
resolve: fn(Box<dyn Any>) -> Pin<Box<dyn Future<Output = AnyView> + Send>>,
|
||||
resolve: for<'a> fn(
|
||||
Box<dyn Any>,
|
||||
ExtraAttrsMut<'a>,
|
||||
)
|
||||
-> Pin<Box<dyn Future<Output = AnyView> + Send + 'a>>,
|
||||
#[cfg(feature = "ssr")]
|
||||
dry_resolve: fn(&mut Box<dyn Any + Send>),
|
||||
dry_resolve: fn(&mut Box<dyn Any + Send>, ExtraAttrsMut<'_>),
|
||||
#[cfg(feature = "hydrate")]
|
||||
#[cfg(feature = "hydrate")]
|
||||
#[allow(clippy::type_complexity)]
|
||||
hydrate_from_server:
|
||||
fn(Box<dyn Any>, &Cursor, &PositionState) -> AnyViewState,
|
||||
hydrate_from_server: fn(
|
||||
Box<dyn Any>,
|
||||
&Cursor,
|
||||
&PositionState,
|
||||
Option<Vec<AnyAttribute>>,
|
||||
) -> AnyViewState,
|
||||
}
|
||||
|
||||
/// Retained view state for [`AnyView`].
|
||||
@@ -128,50 +157,64 @@ where
|
||||
state.insert_before_this(child)
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
fn resolve<'a, T>(
|
||||
value: Box<dyn Any>,
|
||||
extra_attrs: ExtraAttrsMut<'a>,
|
||||
) -> Pin<Box<dyn Future<Output = AnyView> + Send + 'a>>
|
||||
where
|
||||
T: RenderHtml + 'static,
|
||||
{
|
||||
let value = value
|
||||
.downcast::<T>()
|
||||
.expect("AnyView::resolve could not be downcast");
|
||||
Box::pin(async move { value.resolve(extra_attrs).await.into_any() })
|
||||
}
|
||||
|
||||
impl<T> IntoAny for T
|
||||
where
|
||||
T: Send,
|
||||
T: RenderHtml + 'static,
|
||||
T::State: 'static,
|
||||
T: RenderHtml,
|
||||
T::Owned: Send,
|
||||
{
|
||||
fn into_any(self) -> AnyView {
|
||||
#[cfg(feature = "ssr")]
|
||||
let html_len = self.html_len();
|
||||
|
||||
let value = Box::new(self) as Box<dyn Any + Send>;
|
||||
let value = Box::new(self.into_owned()) as Box<dyn Any + Send>;
|
||||
|
||||
match value.downcast::<AnyView>() {
|
||||
// if it's already an AnyView, we don't need to double-wrap it
|
||||
Ok(any_view) => *any_view,
|
||||
Err(value) => {
|
||||
#[cfg(feature = "ssr")]
|
||||
let dry_resolve = |value: &mut Box<dyn Any + Send>| {
|
||||
let value = value
|
||||
.downcast_mut::<T>()
|
||||
.expect("AnyView::resolve could not be downcast");
|
||||
value.dry_resolve();
|
||||
};
|
||||
let html_len =
|
||||
|value: &Box<dyn Any + Send>, extra_attrs: Option<Vec<&AnyAttribute>>| {
|
||||
let value = value
|
||||
.downcast_ref::<T::Owned>()
|
||||
.expect("AnyView::html_len could not be downcast");
|
||||
value.html_len(extra_attrs)
|
||||
};
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
let resolve = |value: Box<dyn Any>| {
|
||||
let value = value
|
||||
.downcast::<T>()
|
||||
.expect("AnyView::resolve could not be downcast");
|
||||
Box::pin(async move { value.resolve().await.into_any() })
|
||||
as Pin<Box<dyn Future<Output = AnyView> + Send>>
|
||||
};
|
||||
let dry_resolve =
|
||||
|value: &mut Box<dyn Any + Send>,
|
||||
extra_attrs: ExtraAttrsMut<'_>| {
|
||||
let value = value
|
||||
.downcast_mut::<T::Owned>()
|
||||
.expect("AnyView::resolve could not be downcast");
|
||||
value.dry_resolve(extra_attrs);
|
||||
};
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
let to_html =
|
||||
|value: Box<dyn Any>,
|
||||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool| {
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>| {
|
||||
let type_id = mark_branches
|
||||
.then(|| format!("{:?}", TypeId::of::<T>()))
|
||||
.then(|| format!("{:?}", TypeId::of::<T::Owned>()))
|
||||
.unwrap_or_default();
|
||||
let value = value
|
||||
.downcast::<T>()
|
||||
.downcast::<T::Owned>()
|
||||
.expect("AnyView::to_html could not be downcast");
|
||||
if mark_branches {
|
||||
buf.open_branch(&type_id);
|
||||
@@ -181,6 +224,7 @@ where
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
if mark_branches {
|
||||
buf.close_branch(&type_id);
|
||||
@@ -192,12 +236,13 @@ where
|
||||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool| {
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>| {
|
||||
let type_id = mark_branches
|
||||
.then(|| format!("{:?}", TypeId::of::<T>()))
|
||||
.then(|| format!("{:?}", TypeId::of::<T::Owned>()))
|
||||
.unwrap_or_default();
|
||||
let value = value
|
||||
.downcast::<T>()
|
||||
.downcast::<T::Owned>()
|
||||
.expect("AnyView::to_html could not be downcast");
|
||||
if mark_branches {
|
||||
buf.open_branch(&type_id);
|
||||
@@ -207,108 +252,98 @@ where
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
if mark_branches {
|
||||
buf.close_branch(&type_id);
|
||||
}
|
||||
};
|
||||
#[cfg(feature = "ssr")]
|
||||
let to_html_async_ooo =
|
||||
|value: Box<dyn Any>,
|
||||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool| {
|
||||
let value = value
|
||||
.downcast::<T>()
|
||||
.expect("AnyView::to_html could not be downcast");
|
||||
value.to_html_async_with_buf::<true>(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
};
|
||||
let build = |value: Box<dyn Any>| {
|
||||
let to_html_async_ooo = |value: Box<dyn Any>,
|
||||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<
|
||||
Vec<AnyAttribute>,
|
||||
>| {
|
||||
let value = value
|
||||
.downcast::<T>()
|
||||
.downcast::<T::Owned>()
|
||||
.expect("AnyView::to_html could not be downcast");
|
||||
value.to_html_async_with_buf::<true>(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
};
|
||||
let build = |value: Box<dyn Any>, extra_attrs: Option<Vec<AnyAttribute>>| {
|
||||
let value = value
|
||||
.downcast::<T::Owned>()
|
||||
.expect("AnyView::build couldn't downcast");
|
||||
let state = Box::new(value.build());
|
||||
let state = Box::new(value.build(extra_attrs));
|
||||
|
||||
AnyViewState {
|
||||
type_id: TypeId::of::<T>(),
|
||||
type_id: TypeId::of::<T::Owned>(),
|
||||
state,
|
||||
|
||||
mount: mount_any::<T>,
|
||||
unmount: unmount_any::<T>,
|
||||
insert_before_this: insert_before_this::<T>,
|
||||
mount: mount_any::<T::Owned>,
|
||||
unmount: unmount_any::<T::Owned>,
|
||||
insert_before_this: insert_before_this::<T::Owned>,
|
||||
}
|
||||
};
|
||||
#[cfg(feature = "hydrate")]
|
||||
let hydrate_from_server =
|
||||
|value: Box<dyn Any>,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState| {
|
||||
let value = value.downcast::<T>().expect(
|
||||
"AnyView::hydrate_from_server couldn't downcast",
|
||||
);
|
||||
let state =
|
||||
Box::new(value.hydrate::<true>(cursor, position));
|
||||
let hydrate_from_server = |value: Box<dyn Any>,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
extra_attrs: Option<
|
||||
Vec<AnyAttribute>,
|
||||
>| {
|
||||
let value = value.downcast::<T::Owned>().expect(
|
||||
"AnyView::hydrate_from_server couldn't downcast",
|
||||
);
|
||||
let state = Box::new(value.hydrate::<true>(
|
||||
cursor,
|
||||
position,
|
||||
extra_attrs,
|
||||
));
|
||||
|
||||
AnyViewState {
|
||||
type_id: TypeId::of::<T>(),
|
||||
state,
|
||||
AnyViewState {
|
||||
type_id: TypeId::of::<T::Owned>(),
|
||||
state,
|
||||
|
||||
mount: mount_any::<T>,
|
||||
unmount: unmount_any::<T>,
|
||||
insert_before_this: insert_before_this::<T>,
|
||||
}
|
||||
};
|
||||
|
||||
let rebuild =
|
||||
|new_type_id: TypeId,
|
||||
value: Box<dyn Any>,
|
||||
state: &mut AnyViewState| {
|
||||
let value = value
|
||||
.downcast::<T>()
|
||||
.expect("AnyView::rebuild couldn't downcast value");
|
||||
if new_type_id == state.type_id {
|
||||
let state = state.state.downcast_mut().expect(
|
||||
"AnyView::rebuild couldn't downcast state",
|
||||
);
|
||||
value.rebuild(state);
|
||||
} else {
|
||||
let mut new = value.into_any().build();
|
||||
state.insert_before_this(&mut new);
|
||||
state.unmount();
|
||||
*state = new;
|
||||
}
|
||||
};
|
||||
|
||||
// Without erasure, tuples of attrs created by default cause too much type explosion to enable.
|
||||
#[cfg(erase_components)]
|
||||
let add_any_attr = |value: Box<dyn Any>, attr: crate::html::attribute::any_attribute::AnyAttribute| {
|
||||
let value = value
|
||||
.downcast::<T>()
|
||||
.expect("AnyView::add_any_attr could not be downcast");
|
||||
value.add_any_attr(attr).into_any()
|
||||
mount: mount_any::<T::Owned>,
|
||||
unmount: unmount_any::<T::Owned>,
|
||||
insert_before_this: insert_before_this::<T::Owned>,
|
||||
}
|
||||
};
|
||||
|
||||
let rebuild =
|
||||
|value: Box<dyn Any>,
|
||||
state: &mut AnyViewState, extra_attrs: Option<Vec<AnyAttribute>>| {
|
||||
let value = value
|
||||
.downcast::<T::Owned>()
|
||||
.expect("AnyView::rebuild couldn't downcast value");
|
||||
let state = state.state.downcast_mut().expect(
|
||||
"AnyView::rebuild couldn't downcast state",
|
||||
);
|
||||
value.rebuild(state, extra_attrs);
|
||||
};
|
||||
|
||||
AnyView {
|
||||
type_id: TypeId::of::<T>(),
|
||||
type_id: TypeId::of::<T::Owned>(),
|
||||
value,
|
||||
extra_attrs: vec![],
|
||||
build,
|
||||
rebuild,
|
||||
// Without erasure, tuples of attrs created by default cause too much type explosion to enable.
|
||||
#[cfg(erase_components)]
|
||||
add_any_attr,
|
||||
#[cfg(feature = "ssr")]
|
||||
resolve,
|
||||
#[cfg(feature = "ssr")]
|
||||
dry_resolve,
|
||||
#[cfg(feature = "ssr")]
|
||||
html_len,
|
||||
#[cfg(feature = "ssr")]
|
||||
resolve: resolve::<T::Owned>,
|
||||
#[cfg(feature = "ssr")]
|
||||
dry_resolve,
|
||||
#[cfg(feature = "ssr")]
|
||||
to_html,
|
||||
#[cfg(feature = "ssr")]
|
||||
to_html_async,
|
||||
@@ -322,15 +357,144 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Ignore, this is a hack for pre use<..> syntax.
|
||||
/// https://github.com/rust-lang/rfcs/blob/master/text/3498-lifetime-capture-rules-2024.md#the-captures-trick
|
||||
pub trait __Captures<T: ?Sized> {}
|
||||
impl<T: ?Sized, U: ?Sized> __Captures<T> for U {}
|
||||
|
||||
/// A mutable view into the extra attributes stored in an [`AnyView`].
|
||||
#[derive(Default)]
|
||||
pub struct ExtraAttrsMut<'a>(Option<Vec<&'a mut Vec<AnyAttribute>>>);
|
||||
impl<'a> ExtraAttrsMut<'a> {
|
||||
/// Create a new mutable view from owned attributes.
|
||||
pub fn from_owned(extra_attrs: &'a mut Option<Vec<AnyAttribute>>) -> Self {
|
||||
match extra_attrs {
|
||||
Some(extra_attrs) => {
|
||||
if extra_attrs.is_empty() {
|
||||
Self(None)
|
||||
} else {
|
||||
Self(Some(vec![extra_attrs]))
|
||||
}
|
||||
}
|
||||
None => Self(None),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
fn add_layer<'b>(
|
||||
mut self,
|
||||
extra_attrs: &'b mut Vec<AnyAttribute>,
|
||||
) -> ExtraAttrsMut<'b>
|
||||
where
|
||||
'a: 'b,
|
||||
{
|
||||
match (self.0, extra_attrs.is_empty()) {
|
||||
(Some(mut extra), false) => {
|
||||
extra.push(extra_attrs);
|
||||
ExtraAttrsMut(Some(extra))
|
||||
}
|
||||
(Some(mut extra), true) => {
|
||||
self.0 = Some(extra);
|
||||
self
|
||||
}
|
||||
(None, false) => ExtraAttrsMut(Some(vec![extra_attrs])),
|
||||
(None, true) => ExtraAttrsMut(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if there are any extra attributes.
|
||||
pub fn is_some(&self) -> bool {
|
||||
match &self.0 {
|
||||
Some(extra) => extra.is_empty(),
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// "clone" the mutable view, to allow reuse in e.g. a for loop.
|
||||
/// The same as .as_deref_mut() on Option<&mut T>.
|
||||
pub fn as_deref_mut(&mut self) -> ExtraAttrsMut<'_> {
|
||||
ExtraAttrsMut(
|
||||
self.0
|
||||
.as_mut()
|
||||
.map(|inner| inner.iter_mut().map(|v| &mut **v).collect()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Iterate over the extra attributes.
|
||||
pub fn iter_mut(
|
||||
&mut self,
|
||||
) -> impl Iterator<Item = &mut AnyAttribute> + __Captures<&'a ()> + '_ {
|
||||
match &mut self.0 {
|
||||
Some(inner) => itertools::Either::Left(
|
||||
inner.iter_mut().flat_map(|v| v.iter_mut()),
|
||||
),
|
||||
None => itertools::Either::Right(std::iter::empty()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Call [`RenderHtml::resolve`] on any extra attributes in parallel.
|
||||
pub async fn resolve(self) {
|
||||
if let Some(extra_attr_groups) = self.0 {
|
||||
futures::future::join_all(extra_attr_groups.into_iter().map(
|
||||
|extra_attrs| async move {
|
||||
*extra_attrs =
|
||||
Attribute::resolve(std::mem::take(extra_attrs)).await;
|
||||
},
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn combine_owned_extra_attrs(
|
||||
parent_extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
extra_attrs: Vec<AnyAttribute>,
|
||||
) -> Option<Vec<AnyAttribute>> {
|
||||
let extra_attrs = if let Some(mut parent_extra_attrs) = parent_extra_attrs {
|
||||
for attr in extra_attrs {
|
||||
parent_extra_attrs.push(attr);
|
||||
}
|
||||
parent_extra_attrs
|
||||
} else {
|
||||
extra_attrs
|
||||
};
|
||||
if extra_attrs.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(extra_attrs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for AnyView {
|
||||
type State = AnyViewState;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
(self.build)(self.value)
|
||||
fn build(self, extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
(self.build)(
|
||||
self.value,
|
||||
combine_owned_extra_attrs(extra_attrs, self.extra_attrs),
|
||||
)
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
(self.rebuild)(self.type_id, self.value, state)
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
if self.type_id == state.type_id {
|
||||
(self.rebuild)(
|
||||
self.value,
|
||||
state,
|
||||
combine_owned_extra_attrs(extra_attrs, self.extra_attrs),
|
||||
)
|
||||
} else {
|
||||
let mut new = (self.build)(
|
||||
self.value,
|
||||
combine_owned_extra_attrs(extra_attrs, self.extra_attrs),
|
||||
);
|
||||
state.insert_before_this(&mut new);
|
||||
state.unmount();
|
||||
*state = new;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,52 +503,60 @@ impl AddAnyAttr for AnyView {
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn add_any_attr<NewAttr: Attribute>(
|
||||
self,
|
||||
mut self,
|
||||
attr: NewAttr,
|
||||
) -> Self::Output<NewAttr>
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml,
|
||||
{
|
||||
// Without erasure, tuples of attrs created by default cause too much type explosion to enable.
|
||||
#[cfg(erase_components)]
|
||||
{
|
||||
use crate::html::attribute::any_attribute::IntoAnyAttribute;
|
||||
|
||||
let attr = attr.into_cloneable_owned();
|
||||
(self.add_any_attr)(self.value, attr.into_any_attr())
|
||||
}
|
||||
#[cfg(not(erase_components))]
|
||||
{
|
||||
self
|
||||
}
|
||||
self.extra_attrs
|
||||
.push(attr.into_cloneable_owned().into_any_attr());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderHtml for AnyView {
|
||||
type AsyncOutput = Self;
|
||||
type Owned = Self;
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>) {
|
||||
#[cfg(feature = "ssr")]
|
||||
{
|
||||
(self.dry_resolve)(&mut self.value)
|
||||
(self.dry_resolve)(
|
||||
&mut self.value,
|
||||
extra_attrs.add_layer(&mut self.extra_attrs),
|
||||
);
|
||||
}
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
panic!(
|
||||
"You are rendering AnyView to HTML without the `ssr` feature \
|
||||
enabled."
|
||||
);
|
||||
{
|
||||
_ = extra_attrs;
|
||||
panic!(
|
||||
"You are rendering AnyView to HTML without the `ssr` feature \
|
||||
enabled."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
async fn resolve(
|
||||
mut self,
|
||||
extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
#[cfg(feature = "ssr")]
|
||||
{
|
||||
(self.resolve)(self.value).await
|
||||
(self.resolve)(
|
||||
self.value,
|
||||
extra_attrs.add_layer(&mut self.extra_attrs),
|
||||
)
|
||||
.await
|
||||
}
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
panic!(
|
||||
"You are rendering AnyView to HTML without the `ssr` feature \
|
||||
enabled."
|
||||
);
|
||||
{
|
||||
_ = extra_attrs;
|
||||
panic!(
|
||||
"You are rendering AnyView to HTML without the `ssr` feature \
|
||||
enabled."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const MIN_LENGTH: usize = 0;
|
||||
@@ -395,15 +567,26 @@ impl RenderHtml for AnyView {
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
#[cfg(feature = "ssr")]
|
||||
(self.to_html)(self.value, buf, position, escape, mark_branches);
|
||||
{
|
||||
(self.to_html)(
|
||||
self.value,
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
combine_owned_extra_attrs(extra_attrs, self.extra_attrs),
|
||||
);
|
||||
}
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
{
|
||||
_ = mark_branches;
|
||||
_ = buf;
|
||||
_ = position;
|
||||
_ = escape;
|
||||
_ = extra_attrs;
|
||||
panic!(
|
||||
"You are rendering AnyView to HTML without the `ssr` feature \
|
||||
enabled."
|
||||
@@ -417,26 +600,31 @@ impl RenderHtml for AnyView {
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
#[cfg(feature = "ssr")]
|
||||
if OUT_OF_ORDER {
|
||||
(self.to_html_async_ooo)(
|
||||
self.value,
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
} else {
|
||||
(self.to_html_async)(
|
||||
self.value,
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
{
|
||||
if OUT_OF_ORDER {
|
||||
(self.to_html_async_ooo)(
|
||||
self.value,
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
combine_owned_extra_attrs(extra_attrs, self.extra_attrs),
|
||||
);
|
||||
} else {
|
||||
(self.to_html_async)(
|
||||
self.value,
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
combine_owned_extra_attrs(extra_attrs, self.extra_attrs),
|
||||
);
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
{
|
||||
@@ -444,6 +632,7 @@ impl RenderHtml for AnyView {
|
||||
_ = position;
|
||||
_ = escape;
|
||||
_ = mark_branches;
|
||||
_ = extra_attrs;
|
||||
panic!(
|
||||
"You are rendering AnyView to HTML without the `ssr` feature \
|
||||
enabled."
|
||||
@@ -455,20 +644,29 @@ impl RenderHtml for AnyView {
|
||||
self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
#[cfg(feature = "hydrate")]
|
||||
if FROM_SERVER {
|
||||
(self.hydrate_from_server)(self.value, cursor, position)
|
||||
} else {
|
||||
panic!(
|
||||
"hydrating AnyView from inside a ViewTemplate is not \
|
||||
supported."
|
||||
);
|
||||
{
|
||||
if FROM_SERVER {
|
||||
(self.hydrate_from_server)(
|
||||
self.value,
|
||||
cursor,
|
||||
position,
|
||||
combine_owned_extra_attrs(extra_attrs, self.extra_attrs),
|
||||
)
|
||||
} else {
|
||||
panic!(
|
||||
"hydrating AnyView from inside a ViewTemplate is not \
|
||||
supported."
|
||||
);
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "hydrate"))]
|
||||
{
|
||||
_ = cursor;
|
||||
_ = position;
|
||||
_ = extra_attrs;
|
||||
panic!(
|
||||
"You are trying to hydrate AnyView without the `hydrate` \
|
||||
feature enabled."
|
||||
@@ -476,16 +674,34 @@ impl RenderHtml for AnyView {
|
||||
}
|
||||
}
|
||||
|
||||
fn html_len(&self) -> usize {
|
||||
fn html_len(&self, extra_attrs: Option<Vec<&AnyAttribute>>) -> usize {
|
||||
#[cfg(feature = "ssr")]
|
||||
{
|
||||
self.html_len
|
||||
(self.html_len)(
|
||||
&self.value,
|
||||
match (extra_attrs, self.extra_attrs.is_empty()) {
|
||||
(Some(mut extra_attrs), false) => {
|
||||
for attr in &self.extra_attrs {
|
||||
extra_attrs.push(attr);
|
||||
}
|
||||
Some(extra_attrs)
|
||||
}
|
||||
(Some(extra_attrs), true) => Some(extra_attrs),
|
||||
(None, false) => Some(self.extra_attrs.iter().collect()),
|
||||
(None, true) => None,
|
||||
},
|
||||
)
|
||||
}
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
{
|
||||
_ = extra_attrs;
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Mountable for AnyViewState {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use super::{
|
||||
add_attr::AddAnyAttr, MarkBranch, Mountable, Position, PositionState,
|
||||
Render, RenderHtml,
|
||||
add_attr::AddAnyAttr, any_view::ExtraAttrsMut, MarkBranch, Mountable,
|
||||
Position, PositionState, Render, RenderHtml,
|
||||
};
|
||||
use crate::{
|
||||
html::attribute::Attribute, hydration::Cursor, ssr::StreamBuilder,
|
||||
html::attribute::{any_attribute::AnyAttribute, Attribute},
|
||||
hydration::Cursor,
|
||||
ssr::StreamBuilder,
|
||||
};
|
||||
use either_of::*;
|
||||
use futures::future::join;
|
||||
@@ -15,29 +17,33 @@ where
|
||||
{
|
||||
type State = Either<A::State, B::State>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
fn build(self, extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
match self {
|
||||
Either::Left(left) => Either::Left(left.build()),
|
||||
Either::Right(right) => Either::Right(right.build()),
|
||||
Either::Left(left) => Either::Left(left.build(extra_attrs)),
|
||||
Either::Right(right) => Either::Right(right.build(extra_attrs)),
|
||||
}
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
match (self, &mut *state) {
|
||||
(Either::Left(new), Either::Left(old)) => {
|
||||
new.rebuild(old);
|
||||
new.rebuild(old, extra_attrs);
|
||||
}
|
||||
(Either::Right(new), Either::Right(old)) => {
|
||||
new.rebuild(old);
|
||||
new.rebuild(old, extra_attrs);
|
||||
}
|
||||
(Either::Right(new), Either::Left(old)) => {
|
||||
let mut new_state = new.build();
|
||||
let mut new_state = new.build(extra_attrs);
|
||||
old.insert_before_this(&mut new_state);
|
||||
old.unmount();
|
||||
*state = Either::Right(new_state);
|
||||
}
|
||||
(Either::Left(new), Either::Right(old)) => {
|
||||
let mut new_state = new.build();
|
||||
let mut new_state = new.build(extra_attrs);
|
||||
old.insert_before_this(&mut new_state);
|
||||
old.unmount();
|
||||
*state = Either::Left(new_state);
|
||||
@@ -120,28 +126,34 @@ where
|
||||
B: RenderHtml,
|
||||
{
|
||||
type AsyncOutput = Either<A::AsyncOutput, B::AsyncOutput>;
|
||||
type Owned = Either<A::Owned, B::Owned>;
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>) {
|
||||
match self {
|
||||
Either::Left(left) => left.dry_resolve(),
|
||||
Either::Right(right) => right.dry_resolve(),
|
||||
Either::Left(left) => left.dry_resolve(extra_attrs),
|
||||
Either::Right(right) => right.dry_resolve(extra_attrs),
|
||||
}
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
async fn resolve(
|
||||
self,
|
||||
extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
match self {
|
||||
Either::Left(left) => Either::Left(left.resolve().await),
|
||||
Either::Right(right) => Either::Right(right.resolve().await),
|
||||
Either::Left(left) => Either::Left(left.resolve(extra_attrs).await),
|
||||
Either::Right(right) => {
|
||||
Either::Right(right.resolve(extra_attrs).await)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const MIN_LENGTH: usize = max_usize(&[A::MIN_LENGTH, B::MIN_LENGTH]);
|
||||
|
||||
#[inline(always)]
|
||||
fn html_len(&self) -> usize {
|
||||
fn html_len(&self, extra_attrs: Option<Vec<&AnyAttribute>>) -> usize {
|
||||
match self {
|
||||
Either::Left(i) => i.html_len(),
|
||||
Either::Right(i) => i.html_len(),
|
||||
Either::Left(i) => i.html_len(extra_attrs),
|
||||
Either::Right(i) => i.html_len(extra_attrs),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,13 +163,20 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
match self {
|
||||
Either::Left(left) => {
|
||||
if mark_branches {
|
||||
buf.open_branch("0");
|
||||
}
|
||||
left.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
left.to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
if mark_branches {
|
||||
buf.close_branch("0");
|
||||
}
|
||||
@@ -166,7 +185,13 @@ where
|
||||
if mark_branches {
|
||||
buf.open_branch("1");
|
||||
}
|
||||
right.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
right.to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
if mark_branches {
|
||||
buf.close_branch("1");
|
||||
}
|
||||
@@ -180,6 +205,7 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -193,6 +219,7 @@ where
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
if mark_branches {
|
||||
buf.close_branch("0");
|
||||
@@ -207,6 +234,7 @@ where
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
if mark_branches {
|
||||
buf.close_branch("1");
|
||||
@@ -219,14 +247,24 @@ where
|
||||
self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
match self {
|
||||
Either::Left(left) => {
|
||||
Either::Left(left.hydrate::<FROM_SERVER>(cursor, position))
|
||||
}
|
||||
Either::Right(right) => {
|
||||
Either::Right(right.hydrate::<FROM_SERVER>(cursor, position))
|
||||
}
|
||||
Either::Left(left) => Either::Left(left.hydrate::<FROM_SERVER>(
|
||||
cursor,
|
||||
position,
|
||||
extra_attrs,
|
||||
)),
|
||||
Either::Right(right) => Either::Right(
|
||||
right.hydrate::<FROM_SERVER>(cursor, position, extra_attrs),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
match self {
|
||||
Either::Left(left) => Either::Left(left.into_owned()),
|
||||
Either::Right(right) => Either::Right(right.into_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -255,25 +293,29 @@ where
|
||||
{
|
||||
type State = EitherKeepAliveState<A::State, B::State>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
fn build(self, extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
let showing_b = self.show_b;
|
||||
let a = self.a.map(Render::build);
|
||||
let b = self.b.map(Render::build);
|
||||
let a = self.a.map(|val| Render::build(val, extra_attrs.clone()));
|
||||
let b = self.b.map(|val| Render::build(val, extra_attrs));
|
||||
EitherKeepAliveState { a, b, showing_b }
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
// set or update A -- `None` just means "no change"
|
||||
match (self.a, &mut state.a) {
|
||||
(Some(new), Some(old)) => new.rebuild(old),
|
||||
(Some(new), None) => state.a = Some(new.build()),
|
||||
(Some(new), Some(old)) => new.rebuild(old, extra_attrs.clone()),
|
||||
(Some(new), None) => state.a = Some(new.build(extra_attrs.clone())),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// set or update B
|
||||
match (self.b, &mut state.b) {
|
||||
(Some(new), Some(old)) => new.rebuild(old),
|
||||
(Some(new), None) => state.b = Some(new.build()),
|
||||
(Some(new), Some(old)) => new.rebuild(old, extra_attrs),
|
||||
(Some(new), None) => state.b = Some(new.build(extra_attrs)),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -333,35 +375,58 @@ where
|
||||
B: RenderHtml,
|
||||
{
|
||||
type AsyncOutput = EitherKeepAlive<A::AsyncOutput, B::AsyncOutput>;
|
||||
type Owned = EitherKeepAlive<A::Owned, B::Owned>;
|
||||
|
||||
const MIN_LENGTH: usize = 0;
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
fn dry_resolve(&mut self, mut extra_attrs: ExtraAttrsMut<'_>) {
|
||||
if let Some(inner) = &mut self.a {
|
||||
inner.dry_resolve();
|
||||
inner.dry_resolve(extra_attrs.as_deref_mut());
|
||||
}
|
||||
if let Some(inner) = &mut self.b {
|
||||
inner.dry_resolve();
|
||||
inner.dry_resolve(extra_attrs);
|
||||
}
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
async fn resolve(
|
||||
self,
|
||||
mut extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
let EitherKeepAlive { a, b, show_b } = self;
|
||||
let (a, b) = join(
|
||||
async move {
|
||||
match a {
|
||||
Some(a) => Some(a.resolve().await),
|
||||
None => None,
|
||||
}
|
||||
},
|
||||
async move {
|
||||
match b {
|
||||
Some(b) => Some(b.resolve().await),
|
||||
None => None,
|
||||
}
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
// Has to be sequential if extra attrs are present:
|
||||
let (a, b) = if extra_attrs.is_some() {
|
||||
let a = match a {
|
||||
Some(a) => Some(a.resolve(extra_attrs.as_deref_mut()).await),
|
||||
None => None,
|
||||
};
|
||||
let b = match b {
|
||||
Some(b) => Some(b.resolve(extra_attrs.as_deref_mut()).await),
|
||||
None => None,
|
||||
};
|
||||
(a, b)
|
||||
} else {
|
||||
join(
|
||||
async move {
|
||||
match a {
|
||||
Some(a) => {
|
||||
Some(a.resolve(ExtraAttrsMut::default()).await)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
},
|
||||
async move {
|
||||
match b {
|
||||
Some(b) => {
|
||||
Some(b.resolve(ExtraAttrsMut::default()).await)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
},
|
||||
)
|
||||
.await
|
||||
};
|
||||
|
||||
EitherKeepAlive { a, b, show_b }
|
||||
}
|
||||
|
||||
@@ -371,15 +436,28 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
if self.show_b {
|
||||
self.b
|
||||
.expect("rendering B to HTML without filling it")
|
||||
.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
.to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
} else {
|
||||
self.a
|
||||
.expect("rendering A to HTML without filling it")
|
||||
.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
.to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,6 +467,7 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -400,6 +479,7 @@ where
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
} else {
|
||||
self.a
|
||||
@@ -409,6 +489,7 @@ where
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -417,25 +498,34 @@ where
|
||||
self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
let showing_b = self.show_b;
|
||||
let a = self.a.map(|a| {
|
||||
if showing_b {
|
||||
a.build()
|
||||
a.build(extra_attrs.clone())
|
||||
} else {
|
||||
a.hydrate::<FROM_SERVER>(cursor, position)
|
||||
a.hydrate::<FROM_SERVER>(cursor, position, extra_attrs.clone())
|
||||
}
|
||||
});
|
||||
let b = self.b.map(|b| {
|
||||
if showing_b {
|
||||
b.hydrate::<FROM_SERVER>(cursor, position)
|
||||
b.hydrate::<FROM_SERVER>(cursor, position, extra_attrs)
|
||||
} else {
|
||||
b.build()
|
||||
b.build(extra_attrs)
|
||||
}
|
||||
});
|
||||
|
||||
EitherKeepAliveState { showing_b, a, b }
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
EitherKeepAlive {
|
||||
a: self.a.map(|a| a.into_owned()),
|
||||
b: self.b.map(|b| b.into_owned()),
|
||||
show_b: self.show_b,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B> Mountable for EitherKeepAliveState<A, B>
|
||||
@@ -535,20 +625,20 @@ macro_rules! tuples {
|
||||
type State = [<EitherOf $num State>]<$($ty,)*>;
|
||||
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
fn build(self, extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
let state = match self {
|
||||
$([<EitherOf $num>]::$ty(this) => [<EitherOf $num>]::$ty(this.build()),)*
|
||||
$([<EitherOf $num>]::$ty(this) => [<EitherOf $num>]::$ty(this.build(extra_attrs)),)*
|
||||
};
|
||||
Self::State { state }
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
fn rebuild(self, state: &mut Self::State, extra_attrs: Option<Vec<AnyAttribute>>) {
|
||||
let new_state = match (self, &mut state.state) {
|
||||
// rebuild same state and return early
|
||||
$(([<EitherOf $num>]::$ty(new), [<EitherOf $num>]::$ty(old)) => { return new.rebuild(old); },)*
|
||||
$(([<EitherOf $num>]::$ty(new), [<EitherOf $num>]::$ty(old)) => { return new.rebuild(old, extra_attrs); },)*
|
||||
// or mount new state
|
||||
$(([<EitherOf $num>]::$ty(new), _) => {
|
||||
let mut new = new.build();
|
||||
let mut new = new.build(extra_attrs);
|
||||
state.insert_before_this(&mut new);
|
||||
[<EitherOf $num>]::$ty(new)
|
||||
},)*
|
||||
@@ -592,38 +682,39 @@ macro_rules! tuples {
|
||||
|
||||
{
|
||||
type AsyncOutput = [<EitherOf $num>]<$($ty::AsyncOutput,)*>;
|
||||
type Owned = [<EitherOf $num>]<$($ty::Owned,)*>;
|
||||
|
||||
const MIN_LENGTH: usize = max_usize(&[$($ty ::MIN_LENGTH,)*]);
|
||||
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>) {
|
||||
match self {
|
||||
$([<EitherOf $num>]::$ty(this) => {
|
||||
this.dry_resolve();
|
||||
this.dry_resolve(extra_attrs);
|
||||
})*
|
||||
}
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
async fn resolve(self, extra_attrs: ExtraAttrsMut<'_>) -> Self::AsyncOutput {
|
||||
match self {
|
||||
$([<EitherOf $num>]::$ty(this) => [<EitherOf $num>]::$ty(this.resolve().await),)*
|
||||
$([<EitherOf $num>]::$ty(this) => [<EitherOf $num>]::$ty(this.resolve(extra_attrs).await),)*
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn html_len(&self) -> usize {
|
||||
fn html_len(&self, extra_attrs: Option<Vec<&AnyAttribute>>) -> usize {
|
||||
match self {
|
||||
$([<EitherOf $num>]::$ty(i) => i.html_len(),)*
|
||||
$([<EitherOf $num>]::$ty(i) => i.html_len(extra_attrs),)*
|
||||
}
|
||||
}
|
||||
|
||||
fn to_html_with_buf(self, buf: &mut String, position: &mut Position, escape: bool, mark_branches: bool) {
|
||||
fn to_html_with_buf(self, buf: &mut String, position: &mut Position, escape: bool, mark_branches: bool, extra_attrs: Option<Vec<AnyAttribute>>) {
|
||||
match self {
|
||||
$([<EitherOf $num>]::$ty(this) => {
|
||||
if mark_branches {
|
||||
buf.open_branch(stringify!($ty));
|
||||
}
|
||||
this.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
this.to_html_with_buf(buf, position, escape, mark_branches, extra_attrs);
|
||||
if mark_branches {
|
||||
buf.close_branch(stringify!($ty));
|
||||
}
|
||||
@@ -633,7 +724,7 @@ macro_rules! tuples {
|
||||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
self,
|
||||
buf: &mut StreamBuilder, position: &mut Position, escape: bool, mark_branches: bool) where
|
||||
buf: &mut StreamBuilder, position: &mut Position, escape: bool, mark_branches: bool, extra_attrs: Option<Vec<AnyAttribute>>) where
|
||||
Self: Sized,
|
||||
{
|
||||
match self {
|
||||
@@ -641,7 +732,7 @@ macro_rules! tuples {
|
||||
if mark_branches {
|
||||
buf.open_branch(stringify!($ty));
|
||||
}
|
||||
this.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape, mark_branches);
|
||||
this.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape, mark_branches, extra_attrs);
|
||||
if mark_branches {
|
||||
buf.close_branch(stringify!($ty));
|
||||
}
|
||||
@@ -653,15 +744,24 @@ macro_rules! tuples {
|
||||
self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
let state = match self {
|
||||
$([<EitherOf $num>]::$ty(this) => {
|
||||
[<EitherOf $num>]::$ty(this.hydrate::<FROM_SERVER>(cursor, position))
|
||||
[<EitherOf $num>]::$ty(this.hydrate::<FROM_SERVER>(cursor, position, extra_attrs))
|
||||
})*
|
||||
};
|
||||
|
||||
Self::State { state }
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
match self {
|
||||
$([<EitherOf $num>]::$ty(this) => {
|
||||
[<EitherOf $num>]::$ty(this.into_owned())
|
||||
})*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use super::{add_attr::AddAnyAttr, Position, PositionState, RenderHtml};
|
||||
use super::{
|
||||
add_attr::AddAnyAttr, any_view::ExtraAttrsMut, Position, PositionState,
|
||||
RenderHtml,
|
||||
};
|
||||
use crate::{
|
||||
html::attribute::Attribute,
|
||||
html::attribute::{any_attribute::AnyAttribute, Attribute},
|
||||
hydration::Cursor,
|
||||
ssr::StreamBuilder,
|
||||
view::{iterators::OptionState, Mountable, Render},
|
||||
@@ -16,19 +19,23 @@ where
|
||||
{
|
||||
type State = ResultState<T>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
fn build(self, extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
let hook = throw_error::get_error_hook();
|
||||
let (state, error) = match self {
|
||||
Ok(view) => (Either::Left(view.build()), None),
|
||||
Ok(view) => (Either::Left(view.build(extra_attrs)), None),
|
||||
Err(e) => (
|
||||
Either::Right(Render::build(())),
|
||||
Either::Right(Render::build((), None)),
|
||||
Some(throw_error::throw(e.into())),
|
||||
),
|
||||
};
|
||||
ResultState { state, error, hook }
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
let _guard = state.hook.clone().map(throw_error::set_error_hook);
|
||||
match (&mut state.state, self) {
|
||||
// both errors: throw the new error and replace
|
||||
@@ -37,11 +44,11 @@ where
|
||||
}
|
||||
// both Ok: need to rebuild child
|
||||
(Either::Left(old), Ok(new)) => {
|
||||
T::rebuild(new, old);
|
||||
T::rebuild(new, old, extra_attrs);
|
||||
}
|
||||
// Ok => Err: unmount, replace with marker, and throw
|
||||
(Either::Left(old), Err(err)) => {
|
||||
let mut new_state = Render::build(());
|
||||
let mut new_state = Render::build((), None);
|
||||
old.insert_before_this(&mut new_state);
|
||||
old.unmount();
|
||||
state.state = Either::Right(new_state);
|
||||
@@ -52,7 +59,7 @@ where
|
||||
if let Some(err) = state.error.take() {
|
||||
throw_error::clear(&err);
|
||||
}
|
||||
let mut new_state = new.build();
|
||||
let mut new_state = new.build(extra_attrs);
|
||||
old.insert_before_this(&mut new_state);
|
||||
old.unmount();
|
||||
state.state = Either::Left(new_state);
|
||||
@@ -132,25 +139,29 @@ where
|
||||
E: Into<AnyError> + Send + 'static,
|
||||
{
|
||||
type AsyncOutput = Result<T::AsyncOutput, E>;
|
||||
type Owned = Result<T::Owned, E>;
|
||||
|
||||
const MIN_LENGTH: usize = T::MIN_LENGTH;
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>) {
|
||||
if let Ok(inner) = self.as_mut() {
|
||||
inner.dry_resolve()
|
||||
inner.dry_resolve(extra_attrs)
|
||||
}
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
async fn resolve(
|
||||
self,
|
||||
extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
match self {
|
||||
Ok(view) => Ok(view.resolve().await),
|
||||
Ok(view) => Ok(view.resolve(extra_attrs).await),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
fn html_len(&self) -> usize {
|
||||
fn html_len(&self, extra_attrs: Option<Vec<&AnyAttribute>>) -> usize {
|
||||
match self {
|
||||
Ok(i) => i.html_len() + 3,
|
||||
Ok(i) => i.html_len(extra_attrs) + 3,
|
||||
Err(_) => 0,
|
||||
}
|
||||
}
|
||||
@@ -161,11 +172,16 @@ where
|
||||
position: &mut super::Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
match self {
|
||||
Ok(inner) => {
|
||||
inner.to_html_with_buf(buf, position, escape, mark_branches)
|
||||
}
|
||||
Ok(inner) => inner.to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
),
|
||||
Err(e) => {
|
||||
buf.push_str("<!>");
|
||||
throw_error::throw(e);
|
||||
@@ -179,6 +195,7 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -188,6 +205,7 @@ where
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
),
|
||||
Err(e) => {
|
||||
buf.push_sync("<!>");
|
||||
@@ -200,19 +218,35 @@ where
|
||||
self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
let hook = throw_error::get_error_hook();
|
||||
let (state, error) = match self {
|
||||
Ok(view) => (
|
||||
Either::Left(view.hydrate::<FROM_SERVER>(cursor, position)),
|
||||
Either::Left(view.hydrate::<FROM_SERVER>(
|
||||
cursor,
|
||||
position,
|
||||
extra_attrs,
|
||||
)),
|
||||
None,
|
||||
),
|
||||
Err(e) => {
|
||||
let state =
|
||||
RenderHtml::hydrate::<FROM_SERVER>((), cursor, position);
|
||||
let state = RenderHtml::hydrate::<FROM_SERVER>(
|
||||
(),
|
||||
cursor,
|
||||
position,
|
||||
extra_attrs,
|
||||
);
|
||||
(Either::Right(state), Some(throw_error::throw(e.into())))
|
||||
}
|
||||
};
|
||||
ResultState { state, error, hook }
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
match self {
|
||||
Ok(view) => Ok(view.into_owned()),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
use super::{
|
||||
add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
|
||||
RenderHtml,
|
||||
add_attr::AddAnyAttr, any_view::ExtraAttrsMut,
|
||||
batch_resolve_items_with_extra_attrs, Mountable, Position, PositionState,
|
||||
Render, RenderHtml,
|
||||
};
|
||||
use crate::{
|
||||
html::attribute::Attribute, hydration::Cursor, renderer::Rndr,
|
||||
html::attribute::{any_attribute::AnyAttribute, Attribute},
|
||||
hydration::Cursor,
|
||||
renderer::Rndr,
|
||||
ssr::StreamBuilder,
|
||||
};
|
||||
use either_of::Either;
|
||||
@@ -18,20 +21,24 @@ where
|
||||
{
|
||||
type State = OptionState<T>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
fn build(self, extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
match self {
|
||||
Some(value) => Either::Left(value),
|
||||
None => Either::Right(()),
|
||||
}
|
||||
.build()
|
||||
.build(extra_attrs)
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
match self {
|
||||
Some(value) => Either::Left(value),
|
||||
None => Either::Right(()),
|
||||
}
|
||||
.rebuild(state)
|
||||
.rebuild(state, extra_attrs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,25 +65,29 @@ where
|
||||
T: RenderHtml,
|
||||
{
|
||||
type AsyncOutput = Option<T::AsyncOutput>;
|
||||
type Owned = Option<T::Owned>;
|
||||
|
||||
const MIN_LENGTH: usize = T::MIN_LENGTH;
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>) {
|
||||
if let Some(inner) = self.as_mut() {
|
||||
inner.dry_resolve();
|
||||
inner.dry_resolve(extra_attrs);
|
||||
}
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
async fn resolve(
|
||||
self,
|
||||
extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
match self {
|
||||
None => None,
|
||||
Some(value) => Some(value.resolve().await),
|
||||
Some(value) => Some(value.resolve(extra_attrs).await),
|
||||
}
|
||||
}
|
||||
|
||||
fn html_len(&self) -> usize {
|
||||
fn html_len(&self, extra_attrs: Option<Vec<&AnyAttribute>>) -> usize {
|
||||
match self {
|
||||
Some(i) => i.html_len() + 3,
|
||||
Some(i) => i.html_len(extra_attrs) + 3,
|
||||
None => 3,
|
||||
}
|
||||
}
|
||||
@@ -87,12 +98,19 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
match self {
|
||||
Some(value) => Either::Left(value),
|
||||
None => Either::Right(()),
|
||||
}
|
||||
.to_html_with_buf(buf, position, escape, mark_branches)
|
||||
.to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
)
|
||||
}
|
||||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
@@ -101,6 +119,7 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -113,6 +132,7 @@ where
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -121,12 +141,17 @@ where
|
||||
self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
match self {
|
||||
Some(value) => Either::Left(value),
|
||||
None => Either::Right(()),
|
||||
}
|
||||
.hydrate::<FROM_SERVER>(cursor, position)
|
||||
.hydrate::<FROM_SERVER>(cursor, position, extra_attrs)
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
self.map(RenderHtml::into_owned)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,20 +161,27 @@ where
|
||||
{
|
||||
type State = VecState<T::State>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
fn build(self, extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
let marker = Rndr::create_placeholder();
|
||||
VecState {
|
||||
states: self.into_iter().map(T::build).collect(),
|
||||
states: self
|
||||
.into_iter()
|
||||
.map(|val| T::build(val, extra_attrs.clone()))
|
||||
.collect(),
|
||||
marker,
|
||||
}
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
let VecState { states, marker } = state;
|
||||
let old = states;
|
||||
// this is an unkeyed diff
|
||||
if old.is_empty() {
|
||||
let mut new = self.build().states;
|
||||
let mut new = self.build(extra_attrs).states;
|
||||
for item in new.iter_mut() {
|
||||
Rndr::mount_before(item, marker.as_ref());
|
||||
}
|
||||
@@ -166,10 +198,10 @@ where
|
||||
for item in self.into_iter().zip_longest(old.iter_mut()) {
|
||||
match item {
|
||||
itertools::EitherOrBoth::Both(new, old) => {
|
||||
T::rebuild(new, old)
|
||||
T::rebuild(new, old, extra_attrs.clone())
|
||||
}
|
||||
itertools::EitherOrBoth::Left(new) => {
|
||||
let mut new_state = new.build();
|
||||
let mut new_state = new.build(extra_attrs.clone());
|
||||
Rndr::mount_before(&mut new_state, marker.as_ref());
|
||||
adds.push(new_state);
|
||||
}
|
||||
@@ -255,24 +287,31 @@ where
|
||||
T: RenderHtml,
|
||||
{
|
||||
type AsyncOutput = Vec<T::AsyncOutput>;
|
||||
type Owned = Vec<T::Owned>;
|
||||
|
||||
const MIN_LENGTH: usize = 0;
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
fn dry_resolve(&mut self, mut extra_attrs: ExtraAttrsMut<'_>) {
|
||||
for inner in self.iter_mut() {
|
||||
inner.dry_resolve();
|
||||
inner.dry_resolve(extra_attrs.as_deref_mut());
|
||||
}
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
futures::future::join_all(self.into_iter().map(T::resolve))
|
||||
async fn resolve(
|
||||
self,
|
||||
extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
batch_resolve_items_with_extra_attrs(self, extra_attrs)
|
||||
.await
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn html_len(&self) -> usize {
|
||||
self.iter().map(|n| n.html_len()).sum::<usize>() + 3
|
||||
fn html_len(&self, extra_attrs: Option<Vec<&AnyAttribute>>) -> usize {
|
||||
self.iter()
|
||||
.map(|n| n.html_len(extra_attrs.clone()))
|
||||
.sum::<usize>()
|
||||
+ 3
|
||||
}
|
||||
|
||||
fn to_html_with_buf(
|
||||
@@ -281,13 +320,26 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
let mut children = self.into_iter();
|
||||
if let Some(first) = children.next() {
|
||||
first.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
first.to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs.clone(),
|
||||
);
|
||||
}
|
||||
for child in children {
|
||||
child.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
child.to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs.clone(),
|
||||
);
|
||||
}
|
||||
buf.push_str("<!>");
|
||||
}
|
||||
@@ -298,6 +350,7 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -308,6 +361,7 @@ where
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs.clone(),
|
||||
);
|
||||
}
|
||||
for child in children {
|
||||
@@ -316,6 +370,7 @@ where
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs.clone(),
|
||||
);
|
||||
}
|
||||
buf.push_sync("<!>");
|
||||
@@ -325,16 +380,27 @@ where
|
||||
self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
let states = self
|
||||
.into_iter()
|
||||
.map(|child| child.hydrate::<FROM_SERVER>(cursor, position))
|
||||
.map(|child| {
|
||||
child.hydrate::<FROM_SERVER>(
|
||||
cursor,
|
||||
position,
|
||||
extra_attrs.clone(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let marker = cursor.next_placeholder(position);
|
||||
|
||||
VecState { states, marker }
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
self.into_iter().map(RenderHtml::into_owned).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> Render for [T; N]
|
||||
@@ -343,19 +409,23 @@ where
|
||||
{
|
||||
type State = ArrayState<T::State, N>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
fn build(self, extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
Self::State {
|
||||
states: self.map(T::build),
|
||||
states: self.map(|val| T::build(val, extra_attrs.clone())),
|
||||
}
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
let Self::State { states } = state;
|
||||
let old = states;
|
||||
// this is an unkeyed diff
|
||||
self.into_iter()
|
||||
.zip(old.iter_mut())
|
||||
.for_each(|(new, old)| T::rebuild(new, old));
|
||||
.for_each(|(new, old)| T::rebuild(new, old, extra_attrs.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,17 +487,21 @@ where
|
||||
T: RenderHtml,
|
||||
{
|
||||
type AsyncOutput = [T::AsyncOutput; N];
|
||||
type Owned = Vec<T::Owned>;
|
||||
|
||||
const MIN_LENGTH: usize = 0;
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
fn dry_resolve(&mut self, mut extra_attrs: ExtraAttrsMut<'_>) {
|
||||
for inner in self.iter_mut() {
|
||||
inner.dry_resolve();
|
||||
inner.dry_resolve(extra_attrs.as_deref_mut());
|
||||
}
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
futures::future::join_all(self.into_iter().map(T::resolve))
|
||||
async fn resolve(
|
||||
self,
|
||||
extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
batch_resolve_items_with_extra_attrs(self, extra_attrs)
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>()
|
||||
@@ -435,8 +509,10 @@ where
|
||||
.unwrap_or_else(|_| unreachable!())
|
||||
}
|
||||
|
||||
fn html_len(&self) -> usize {
|
||||
self.iter().map(RenderHtml::html_len).sum::<usize>()
|
||||
fn html_len(&self, extra_attrs: Option<Vec<&AnyAttribute>>) -> usize {
|
||||
self.iter()
|
||||
.map(|val| RenderHtml::html_len(val, extra_attrs.clone()))
|
||||
.sum::<usize>()
|
||||
}
|
||||
|
||||
fn to_html_with_buf(
|
||||
@@ -445,9 +521,16 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
for child in self.into_iter() {
|
||||
child.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
child.to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,6 +540,7 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -466,6 +550,7 @@ where
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -474,9 +559,17 @@ where
|
||||
self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
let states =
|
||||
self.map(|child| child.hydrate::<FROM_SERVER>(cursor, position));
|
||||
let states = self.map(|child| {
|
||||
child.hydrate::<FROM_SERVER>(cursor, position, extra_attrs.clone())
|
||||
});
|
||||
ArrayState { states }
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
self.into_iter()
|
||||
.map(RenderHtml::into_owned)
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use super::{
|
||||
add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
|
||||
RenderHtml,
|
||||
add_attr::AddAnyAttr, any_view::ExtraAttrsMut,
|
||||
batch_resolve_items_with_extra_attrs, Mountable, Position, PositionState,
|
||||
Render, RenderHtml,
|
||||
};
|
||||
use crate::{
|
||||
html::attribute::Attribute,
|
||||
html::attribute::{any_attribute::AnyAttribute, Attribute},
|
||||
hydration::Cursor,
|
||||
renderer::{CastFrom, Rndr},
|
||||
ssr::StreamBuilder,
|
||||
@@ -75,7 +76,7 @@ where
|
||||
type State = KeyedState<K, VFS, V>;
|
||||
// TODO fallible state and try_build()/try_rebuild() here
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
fn build(self, extra_attrs: Option<Vec<AnyAttribute>>) -> Self::State {
|
||||
let items = self.items.into_iter();
|
||||
let (capacity, _) = items.size_hint();
|
||||
let mut hashed_items =
|
||||
@@ -84,7 +85,8 @@ where
|
||||
for (index, item) in items.enumerate() {
|
||||
hashed_items.insert((self.key_fn)(&item));
|
||||
let (set_index, view) = (self.view_fn)(index, item);
|
||||
rendered_items.push(Some((set_index, view.build())));
|
||||
rendered_items
|
||||
.push(Some((set_index, view.build(extra_attrs.clone()))));
|
||||
}
|
||||
KeyedState {
|
||||
parent: None,
|
||||
@@ -94,7 +96,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
fn rebuild(
|
||||
self,
|
||||
state: &mut Self::State,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
let KeyedState {
|
||||
parent,
|
||||
marker,
|
||||
@@ -123,6 +129,7 @@ where
|
||||
rendered_items,
|
||||
&self.view_fn,
|
||||
items,
|
||||
extra_attrs,
|
||||
);
|
||||
|
||||
*hashed_items = new_hashed_items;
|
||||
@@ -131,9 +138,9 @@ where
|
||||
|
||||
impl<T, I, K, KF, VF, VFS, V> AddAnyAttr for Keyed<T, I, K, KF, VF, VFS, V>
|
||||
where
|
||||
I: IntoIterator<Item = T> + Send,
|
||||
I: IntoIterator<Item = T> + Send + 'static,
|
||||
K: Eq + Hash + 'static,
|
||||
KF: Fn(&T) -> K + Send,
|
||||
KF: Fn(&T) -> K + Send + 'static,
|
||||
V: RenderHtml,
|
||||
V: 'static,
|
||||
VF: Fn(usize, T) -> (VFS, V) + Send + 'static,
|
||||
@@ -184,29 +191,38 @@ where
|
||||
|
||||
impl<T, I, K, KF, VF, VFS, V> RenderHtml for Keyed<T, I, K, KF, VF, VFS, V>
|
||||
where
|
||||
I: IntoIterator<Item = T> + Send,
|
||||
I: IntoIterator<Item = T> + Send + 'static,
|
||||
K: Eq + Hash + 'static,
|
||||
KF: Fn(&T) -> K + Send,
|
||||
KF: Fn(&T) -> K + Send + 'static,
|
||||
V: RenderHtml + 'static,
|
||||
VF: Fn(usize, T) -> (VFS, V) + Send + 'static,
|
||||
VFS: Fn(usize) + 'static,
|
||||
T: 'static,
|
||||
{
|
||||
type AsyncOutput = Vec<V::AsyncOutput>; // TODO
|
||||
type Owned = Keyed<T, I, K, KF, VF, VFS, V>;
|
||||
|
||||
const MIN_LENGTH: usize = 0;
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {
|
||||
// TODO...
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
futures::future::join_all(self.items.into_iter().enumerate().map(
|
||||
|(index, item)| {
|
||||
let (_, view) = (self.view_fn)(index, item);
|
||||
view.resolve()
|
||||
},
|
||||
))
|
||||
async fn resolve(
|
||||
self,
|
||||
extra_attrs: ExtraAttrsMut<'_>,
|
||||
) -> Self::AsyncOutput {
|
||||
batch_resolve_items_with_extra_attrs(
|
||||
self.items
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, item)| {
|
||||
let (_, view) = (self.view_fn)(index, item);
|
||||
view
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
extra_attrs,
|
||||
)
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>()
|
||||
@@ -218,10 +234,17 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
for (index, item) in self.items.into_iter().enumerate() {
|
||||
let (_, item) = (self.view_fn)(index, item);
|
||||
item.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
item.to_html_with_buf(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs.clone(),
|
||||
);
|
||||
*position = Position::NextChild;
|
||||
}
|
||||
buf.push_str("<!>");
|
||||
@@ -233,6 +256,7 @@ where
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) {
|
||||
for (index, item) in self.items.into_iter().enumerate() {
|
||||
let (_, item) = (self.view_fn)(index, item);
|
||||
@@ -241,6 +265,7 @@ where
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs.clone(),
|
||||
);
|
||||
*position = Position::NextChild;
|
||||
}
|
||||
@@ -251,6 +276,7 @@ where
|
||||
self,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) -> Self::State {
|
||||
// get parent and position
|
||||
let current = cursor.current();
|
||||
@@ -272,7 +298,11 @@ where
|
||||
for (index, item) in items.enumerate() {
|
||||
hashed_items.insert((self.key_fn)(&item));
|
||||
let (set_index, view) = (self.view_fn)(index, item);
|
||||
let item = view.hydrate::<FROM_SERVER>(cursor, position);
|
||||
let item = view.hydrate::<FROM_SERVER>(
|
||||
cursor,
|
||||
position,
|
||||
extra_attrs.clone(),
|
||||
);
|
||||
rendered_items.push(Some((set_index, item)));
|
||||
}
|
||||
let marker = cursor.next_placeholder(position);
|
||||
@@ -283,6 +313,10 @@ where
|
||||
rendered_items,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, VFS, V> Mountable for KeyedState<K, VFS, V>
|
||||
@@ -510,6 +544,7 @@ fn apply_diff<T, VFS, V>(
|
||||
children: &mut Vec<Option<(VFS, V::State)>>,
|
||||
view_fn: impl Fn(usize, T) -> (VFS, V),
|
||||
mut items: Vec<Option<T>>,
|
||||
extra_attrs: Option<Vec<AnyAttribute>>,
|
||||
) where
|
||||
VFS: Fn(usize),
|
||||
V: Render,
|
||||
@@ -583,7 +618,7 @@ fn apply_diff<T, VFS, V>(
|
||||
for DiffOpAdd { at, mode } in add_cmds {
|
||||
let item = items[at].take().unwrap();
|
||||
let (set_index, item) = view_fn(at, item);
|
||||
let mut item = item.build();
|
||||
let mut item = item.build(extra_attrs.clone());
|
||||
|
||||
match mode {
|
||||
DiffOpAddMode::Normal => {
|
||||
@@ -694,7 +729,7 @@ mod tests {
|
||||
#[test]
|
||||
fn keyed_creates_list() {
|
||||
let el = ul((), keyed(1..=3, |k| *k, item));
|
||||
let el_state = el.build();
|
||||
let el_state = el.build(None);
|
||||
assert_eq!(
|
||||
el_state.el.to_debug_html(),
|
||||
"<ul><li>1</li><li>2</li><li>3</li></ul>"
|
||||
@@ -704,7 +739,7 @@ mod tests {
|
||||
#[test]
|
||||
fn adding_items_updates_list() {
|
||||
let el = ul((), keyed(1..=3, |k| *k, item));
|
||||
let mut el_state = el.build();
|
||||
let mut el_state = el.build(None);
|
||||
let el = ul((), keyed(1..=5, |k| *k, item));
|
||||
el.rebuild(&mut el_state);
|
||||
assert_eq!(
|
||||
@@ -716,7 +751,7 @@ mod tests {
|
||||
#[test]
|
||||
fn removing_items_updates_list() {
|
||||
let el = ul((), keyed(1..=3, |k| *k, item));
|
||||
let mut el_state = el.build();
|
||||
let mut el_state = el.build(None);
|
||||
let el = ul((), keyed(1..=2, |k| *k, item));
|
||||
el.rebuild(&mut el_state);
|
||||
assert_eq!(
|
||||
@@ -728,7 +763,7 @@ mod tests {
|
||||
#[test]
|
||||
fn swapping_items_updates_list() {
|
||||
let el = ul((), keyed([1, 2, 3, 4, 5], |k| *k, item));
|
||||
let mut el_state = el.build();
|
||||
let mut el_state = el.build(None);
|
||||
let el = ul((), keyed([1, 4, 3, 2, 5], |k| *k, item));
|
||||
el.rebuild(&mut el_state);
|
||||
assert_eq!(
|
||||
@@ -740,7 +775,7 @@ mod tests {
|
||||
#[test]
|
||||
fn swapping_and_removing_orders_correctly() {
|
||||
let el = ul((), keyed([1, 2, 3, 4, 5], |k| *k, item));
|
||||
let mut el_state = el.build();
|
||||
let mut el_state = el.build(None);
|
||||
let el = ul((), keyed([1, 4, 3, 5], |k| *k, item));
|
||||
el.rebuild(&mut el_state);
|
||||
assert_eq!(
|
||||
@@ -752,7 +787,7 @@ mod tests {
|
||||
#[test]
|
||||
fn arbitrarily_hard_adjustment() {
|
||||
let el = ul((), keyed([1, 2, 3, 4, 5], |k| *k, item));
|
||||
let mut el_state = el.build();
|
||||
let mut el_state = el.build(None);
|
||||
let el = ul((), keyed([2, 4, 3], |k| *k, item));
|
||||
el.rebuild(&mut el_state);
|
||||
assert_eq!(
|
||||
@@ -764,7 +799,7 @@ mod tests {
|
||||
#[test]
|
||||
fn a_series_of_moves() {
|
||||
let el = ul((), keyed([1, 2, 3, 4, 5], |k| *k, item));
|
||||
let mut el_state = el.build();
|
||||
let mut el_state = el.build(None);
|
||||
let el = ul((), keyed([2, 4, 3], |k| *k, item));
|
||||
el.rebuild(&mut el_state);
|
||||
let el = ul((), keyed([1, 7, 5, 11, 13, 17], |k| *k, item));
|
||||
@@ -784,7 +819,7 @@ mod tests {
|
||||
#[test]
|
||||
fn clearing_works() {
|
||||
let el = ul((), keyed([1, 2, 3, 4, 5], |k| *k, item));
|
||||
let mut el_state = el.build();
|
||||
let mut el_state = el.build(None);
|
||||
let el = ul((), keyed([], |k| *k, item));
|
||||
el.rebuild(&mut el_state);
|
||||
assert_eq!(el_state.el.to_debug_html(), "<ul></ul>");
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user