mirror of
https://github.com/leptos-rs/leptos.git
synced 2026-01-11 18:56:16 -05:00
Compare commits
10 Commits
main
...
leptos_0.9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9b17e2a47 | ||
|
|
9cdab54329 | ||
|
|
5d23af8581 | ||
|
|
1d268ab3a9 | ||
|
|
55b3752983 | ||
|
|
53299e4599 | ||
|
|
2d6731e508 | ||
|
|
d8b371b26b | ||
|
|
8326e514c2 | ||
|
|
6eebd71868 |
17
Cargo.lock
generated
17
Cargo.lock
generated
@@ -1316,7 +1316,7 @@ dependencies = [
|
||||
"gobject-sys",
|
||||
"libc",
|
||||
"system-deps",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1672,7 +1672,7 @@ dependencies = [
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2 0.6.1",
|
||||
"socket2 0.5.10",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -2810,7 +2810,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"socket2 0.6.1",
|
||||
"tracing",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3367,11 +3367,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_qs"
|
||||
version = "0.15.0"
|
||||
version = "1.0.0-rc.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3faaf9e727533a19351a43cc5a8de957372163c7d35cc48c90b75cdda13c352"
|
||||
checksum = "4cb0b9062a400c31442e67d1f2b1e7746bebd691110ebee1b7d0c7293b04fab1"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"percent-encoding",
|
||||
"ryu",
|
||||
"serde",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
@@ -3464,6 +3466,7 @@ dependencies = [
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"trybuild",
|
||||
"typed-builder",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
@@ -3820,7 +3823,7 @@ dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4565,7 +4568,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
12
Cargo.toml
12
Cargo.toml
@@ -103,12 +103,12 @@ base64 = { default-features = false, version = "0.22.1" }
|
||||
cfg-if = { default-features = false, version = "1.0.4" }
|
||||
wasm-bindgen-futures = { default-features = false, version = "0.4.56" }
|
||||
tower = { default-features = false, version = "0.5.2" }
|
||||
proc-macro2 = { default-features = false, version = "1.0.104" }
|
||||
serde = { default-features = false, version = "1.0.228" }
|
||||
parking_lot = { default-features = false, version = "0.12.5" }
|
||||
axum = { default-features = false, version = "0.8.8" }
|
||||
serde_qs = { default-features = false, version = "0.15.0" }
|
||||
syn = { default-features = false, version = "2.0.111" }
|
||||
proc-macro2 = { default-features = false, version = "1.0.96" }
|
||||
serde = { default-features = false, version = "1.0.219" }
|
||||
parking_lot = { default-features = false, version = "0.12.4" }
|
||||
axum = { default-features = false, version = "0.8.4" }
|
||||
serde_qs = { default-features = false, version = "1.0.0-rc.3" }
|
||||
syn = { default-features = false, version = "2.0.104" }
|
||||
xxhash-rust = { default-features = false, version = "0.8.15" }
|
||||
paste = { default-features = false, version = "1.0.15" }
|
||||
quote = { default-features = false, version = "1.0.42" }
|
||||
|
||||
@@ -670,7 +670,7 @@ fn CodeDemoWasm(mode: WasmDemo) -> impl IntoView {
|
||||
leptos::logging::log!("wasm csr_listener listener added");
|
||||
|
||||
// Dispatch the event when this view is finally mounted onto the DOM.
|
||||
request_animation_frame(move || {
|
||||
_ = request_animation_frame(move || {
|
||||
let event = web_sys::Event::new("hljs_hook")
|
||||
.expect("error creating hljs_hook event");
|
||||
document.dispatch_event(&event)
|
||||
@@ -689,7 +689,7 @@ fn CodeDemoWasm(mode: WasmDemo) -> impl IntoView {
|
||||
<Suspense fallback=move || view! { <p>"Loading code example..."</p> }>{
|
||||
move || Suspend::new(async move {
|
||||
Effect::new(move |_| {
|
||||
request_animation_frame(move || {
|
||||
_ = request_animation_frame(move || {
|
||||
leptos::logging::log!("request_animation_frame invoking hljs::highlight_all");
|
||||
// under SSR this is an noop, but it wouldn't be called under there anyway because
|
||||
// it isn't the isomorphic version, i.e. Effect::new_isomorphic(...).
|
||||
@@ -815,7 +815,7 @@ fn WasmBindgenJSHookReadyEvent() -> impl IntoView {
|
||||
leptos::logging::log!("wasm csr_listener listener added");
|
||||
|
||||
// Dispatch the event when this view is finally mounted onto the DOM.
|
||||
request_animation_frame(move || {
|
||||
_ = request_animation_frame(move || {
|
||||
let event = web_sys::Event::new("hljs_hook")
|
||||
.expect("error creating hljs_hook event");
|
||||
document.dispatch_event(&event)
|
||||
@@ -866,7 +866,7 @@ fn WasmBindgenEffect() -> impl IntoView {
|
||||
let example = r#"<Suspense fallback=move || view! { <p>"Loading code example..."</p> }>{
|
||||
move || Suspend::new(async move {
|
||||
Effect::new(move |_| {
|
||||
request_animation_frame(move || {
|
||||
_ = request_animation_frame(move || {
|
||||
leptos::logging::log!("request_animation_frame invoking hljs::highlight_all");
|
||||
// under SSR this is an noop.
|
||||
crate::hljs::highlight_all();
|
||||
|
||||
@@ -10,7 +10,7 @@ crate-type = ["cdylib", "rlib"]
|
||||
axum = { version = "0.8.1", optional = true }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
console_log = "1.0"
|
||||
leptos = { path = "../../leptos", features = ["tracing"] }
|
||||
leptos = { path = "../../leptos", features = ["tracing", "lazy"] }
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
leptos_router = { path = "../../router" }
|
||||
|
||||
@@ -97,7 +97,7 @@ fn delay(
|
||||
duration: Duration,
|
||||
) -> impl Future<Output = Result<(), Canceled>> + Send {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
set_timeout(
|
||||
_ = set_timeout(
|
||||
move || {
|
||||
_ = tx.send(());
|
||||
},
|
||||
|
||||
@@ -50,7 +50,7 @@ where
|
||||
};
|
||||
|
||||
// here, we return the handle
|
||||
set_interval_with_handle(
|
||||
set_interval(
|
||||
f.clone(),
|
||||
// this is the only reactive access, so this effect will only
|
||||
// re-run when the interval changes
|
||||
|
||||
@@ -684,7 +684,7 @@ where
|
||||
additional_context.clone(),
|
||||
app_fn.clone(),
|
||||
);
|
||||
let asyn = render_app_async_stream_with_context(
|
||||
let asyn = render_app_async_with_context(
|
||||
additional_context.clone(),
|
||||
app_fn.clone(),
|
||||
);
|
||||
@@ -1019,73 +1019,6 @@ where
|
||||
render_app_async_with_context(|| {}, app_fn)
|
||||
}
|
||||
|
||||
/// Returns an Axum [Handler](axum::handler::Handler) that listens for a `GET` request and tries
|
||||
/// to route it using [leptos_router], asynchronously rendering an HTML page after all
|
||||
/// `async` resources have loaded.
|
||||
///
|
||||
/// This version allows us to pass Axum State/Extension/Extractor or other info from Axum or network
|
||||
/// layers above Leptos itself. To use it, you'll need to write your own handler function that provides
|
||||
/// the data to leptos in a closure. An example is below
|
||||
/// ```
|
||||
/// use axum::{
|
||||
/// body::Body,
|
||||
/// extract::Path,
|
||||
/// http::Request,
|
||||
/// response::{IntoResponse, Response},
|
||||
/// };
|
||||
/// use leptos::context::provide_context;
|
||||
///
|
||||
/// async fn custom_handler(
|
||||
/// Path(id): Path<String>,
|
||||
/// req: Request<Body>,
|
||||
/// ) -> Response {
|
||||
/// let handler = leptos_axum::render_app_async_with_context(
|
||||
/// move || {
|
||||
/// provide_context(id.clone());
|
||||
/// },
|
||||
/// || { /* your application here */ },
|
||||
/// );
|
||||
/// handler(req).await.into_response()
|
||||
/// }
|
||||
/// ```
|
||||
/// Otherwise, this function is identical to [render_app_to_stream].
|
||||
///
|
||||
/// ## Provided Context Types
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [`Parts`]
|
||||
/// - [`ResponseOptions`]
|
||||
/// - [`ServerMetaContext`]
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
)]
|
||||
pub fn render_app_async_stream_with_context<IV>(
|
||||
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
|
||||
+ 'static
|
||||
where
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
handle_response(additional_context, app_fn, |app, chunks, _supports_ooo| {
|
||||
Box::pin(async move {
|
||||
let app = if cfg!(feature = "islands-router") {
|
||||
app.to_html_stream_in_order_branching()
|
||||
} else {
|
||||
app.to_html_stream_in_order()
|
||||
};
|
||||
let app = app.collect::<String>().await;
|
||||
let chunks = chunks();
|
||||
Box::pin(once(async move { app }).chain(chunks))
|
||||
as PinnedStream<String>
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns an Axum [Handler](axum::handler::Handler) that listens for a `GET` request and tries
|
||||
/// to route it using [leptos_router], asynchronously rendering an HTML page after all
|
||||
/// `async` resources have loaded.
|
||||
@@ -2072,19 +2005,7 @@ where
|
||||
},
|
||||
move || shell(options),
|
||||
req,
|
||||
|app, chunks, _supports_ooo| {
|
||||
Box::pin(async move {
|
||||
let app = if cfg!(feature = "islands-router") {
|
||||
app.to_html_stream_in_order_branching()
|
||||
} else {
|
||||
app.to_html_stream_in_order()
|
||||
};
|
||||
let app = app.collect::<String>().await;
|
||||
let chunks = chunks();
|
||||
Box::pin(once(async move { app }).chain(chunks))
|
||||
as PinnedStream<String>
|
||||
})
|
||||
},
|
||||
async_stream_builder,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
||||
@@ -122,6 +122,7 @@ subsecond = [
|
||||
"web-sys/WebSocket",
|
||||
"web-sys/Window",
|
||||
]
|
||||
lazy = ["tachys/lazy"]
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { features = [
|
||||
|
||||
@@ -83,7 +83,7 @@ pub fn AnimatedShow(
|
||||
} else {
|
||||
cls.set(hide_class);
|
||||
|
||||
let h = leptos_dom::helpers::set_timeout_with_handle(
|
||||
let h = leptos_dom::helpers::set_timeout(
|
||||
move || show.set(false),
|
||||
hide_delay,
|
||||
)
|
||||
|
||||
@@ -287,7 +287,9 @@ where
|
||||
web_sys::UrlSearchParams::new_with_str_sequence_sequence(form_data)
|
||||
.unwrap_throw();
|
||||
let data = data.to_string().as_string().unwrap_or_default();
|
||||
serde_qs::Config::new(5, false).deserialize_str::<Self>(&data)
|
||||
serde_qs::Config::new()
|
||||
.use_form_encoding(true)
|
||||
.deserialize_str::<Self>(&data)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,14 +73,14 @@ where
|
||||
let to = to.into_iter().collect::<Vec<_>>();
|
||||
|
||||
let (list, set_list) = create_signal(from.clone());
|
||||
request_animation_frame({
|
||||
_ = request_animation_frame({
|
||||
let to = to.clone();
|
||||
let then = then.clone();
|
||||
move || {
|
||||
set_list(to);
|
||||
|
||||
if let Some(then) = then {
|
||||
request_animation_frame({
|
||||
_ = request_animation_frame({
|
||||
move || {
|
||||
set_list(then);
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ pub fn event_target_checked(ev: &web_sys::Event) -> bool {
|
||||
.checked()
|
||||
}
|
||||
|
||||
/// Handle that is generated by [request_animation_frame_with_handle] and can
|
||||
/// Handle that is generated by [request_animation_frame] and can
|
||||
/// be used to cancel the animation frame request.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct AnimationFrameRequestHandle(i32);
|
||||
@@ -129,18 +129,6 @@ impl AnimationFrameRequestHandle {
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs the given function between the next repaint using
|
||||
/// [`Window.requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame).
|
||||
///
|
||||
/// ### Note about Context
|
||||
///
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
#[cfg_attr(feature = "tracing", instrument(level = "trace", skip_all))]
|
||||
#[inline(always)]
|
||||
pub fn request_animation_frame(cb: impl FnOnce() + 'static) {
|
||||
_ = request_animation_frame_with_handle(cb);
|
||||
}
|
||||
|
||||
// Closure::once_into_js only frees the callback when it's actually
|
||||
// called, so this instead uses into_js_value, which can be freed by
|
||||
// the host JS engine's GC if it supports weak references (which all
|
||||
@@ -169,7 +157,7 @@ fn closure_once(cb: impl FnOnce() + 'static) -> JsValue {
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
#[cfg_attr(feature = "tracing", instrument(level = "trace", skip_all))]
|
||||
#[inline(always)]
|
||||
pub fn request_animation_frame_with_handle(
|
||||
pub fn request_animation_frame(
|
||||
cb: impl FnOnce() + 'static,
|
||||
) -> Result<AnimationFrameRequestHandle, JsValue> {
|
||||
#[cfg(feature = "tracing")]
|
||||
@@ -190,7 +178,7 @@ pub fn request_animation_frame_with_handle(
|
||||
raf(closure_once(cb))
|
||||
}
|
||||
|
||||
/// Handle that is generated by [request_idle_callback_with_handle] and can be
|
||||
/// Handle that is generated by [request_idle_callback] and can be
|
||||
/// used to cancel the idle callback.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct IdleCallbackHandle(u32);
|
||||
@@ -203,18 +191,6 @@ impl IdleCallbackHandle {
|
||||
}
|
||||
}
|
||||
|
||||
/// Queues the given function during an idle period using
|
||||
/// [`Window.requestIdleCallback`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestIdleCallback).
|
||||
///
|
||||
/// ### Note about Context
|
||||
///
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
#[cfg_attr(feature = "tracing", instrument(level = "trace", skip_all))]
|
||||
#[inline(always)]
|
||||
pub fn request_idle_callback(cb: impl Fn() + 'static) {
|
||||
_ = request_idle_callback_with_handle(cb);
|
||||
}
|
||||
|
||||
/// Queues the given function during an idle period using
|
||||
/// [`Window.requestIdleCallback`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestIdleCallback),
|
||||
/// returning a cancelable handle.
|
||||
@@ -224,7 +200,7 @@ pub fn request_idle_callback(cb: impl Fn() + 'static) {
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
#[cfg_attr(feature = "tracing", instrument(level = "trace", skip_all))]
|
||||
#[inline(always)]
|
||||
pub fn request_idle_callback_with_handle(
|
||||
pub fn request_idle_callback(
|
||||
cb: impl Fn() + 'static,
|
||||
) -> Result<IdleCallbackHandle, JsValue> {
|
||||
#[cfg(feature = "tracing")]
|
||||
@@ -261,7 +237,7 @@ pub fn queue_microtask(task: impl FnOnce() + 'static) {
|
||||
tachys::renderer::dom::queue_microtask(task);
|
||||
}
|
||||
|
||||
/// Handle that is generated by [set_timeout_with_handle] and can be used to clear the timeout.
|
||||
/// Handle that is generated by [set_timeout] and can be used to clear the timeout.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct TimeoutHandle(i32);
|
||||
|
||||
@@ -273,20 +249,6 @@ impl TimeoutHandle {
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes the given function after the given duration of time has passed.
|
||||
/// [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout).
|
||||
///
|
||||
/// ### Note about Context
|
||||
///
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
instrument(level = "trace", skip_all, fields(duration = ?duration))
|
||||
)]
|
||||
pub fn set_timeout(cb: impl FnOnce() + 'static, duration: Duration) {
|
||||
_ = set_timeout_with_handle(cb, duration);
|
||||
}
|
||||
|
||||
/// Executes the given function after the given duration of time has passed, returning a cancelable handle.
|
||||
/// [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout).
|
||||
///
|
||||
@@ -298,7 +260,7 @@ pub fn set_timeout(cb: impl FnOnce() + 'static, duration: Duration) {
|
||||
instrument(level = "trace", skip_all, fields(duration = ?duration))
|
||||
)]
|
||||
#[inline(always)]
|
||||
pub fn set_timeout_with_handle(
|
||||
pub fn set_timeout(
|
||||
cb: impl FnOnce() + 'static,
|
||||
duration: Duration,
|
||||
) -> Result<TimeoutHandle, JsValue> {
|
||||
@@ -391,7 +353,7 @@ pub fn debounce<T: 'static>(
|
||||
if let Some(timer) = timer.write().unwrap().take() {
|
||||
timer.clear();
|
||||
}
|
||||
let handle = set_timeout_with_handle(
|
||||
let handle = set_timeout(
|
||||
{
|
||||
let cb = Arc::clone(&cb);
|
||||
move || {
|
||||
@@ -418,20 +380,6 @@ impl IntervalHandle {
|
||||
}
|
||||
}
|
||||
|
||||
/// Repeatedly calls the given function, with a delay of the given duration between calls.
|
||||
/// See [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval).
|
||||
///
|
||||
/// ### Note about Context
|
||||
///
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
instrument(level = "trace", skip_all, fields(duration = ?duration))
|
||||
)]
|
||||
pub fn set_interval(cb: impl Fn() + 'static, duration: Duration) {
|
||||
_ = set_interval_with_handle(cb, duration);
|
||||
}
|
||||
|
||||
/// Repeatedly calls the given function, with a delay of the given duration between calls,
|
||||
/// returning a cancelable handle.
|
||||
/// See [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval).
|
||||
@@ -444,7 +392,7 @@ pub fn set_interval(cb: impl Fn() + 'static, duration: Duration) {
|
||||
instrument(level = "trace", skip_all, fields(duration = ?duration))
|
||||
)]
|
||||
#[inline(always)]
|
||||
pub fn set_interval_with_handle(
|
||||
pub fn set_interval(
|
||||
cb: impl Fn() + 'static,
|
||||
duration: Duration,
|
||||
) -> Result<IntervalHandle, JsValue> {
|
||||
|
||||
@@ -20,7 +20,7 @@ pub fn Demo1() -> impl IntoView {
|
||||
|
||||
// We need to add the 3D view onto the canvas post render.
|
||||
Effect::new(move |_| {
|
||||
request_animation_frame(move || {
|
||||
_ = request_animation_frame(move || {
|
||||
scene_sig.get_untracked().setup();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -95,7 +95,7 @@ pub fn App() -> impl IntoView {
|
||||
// if expires_in isn't 0, then set a timeout that rerfresh a minute short of the refresh.
|
||||
let expires_in = rw_expires_in.get();
|
||||
if expires_in != 0 && email.get_untracked().is_some() {
|
||||
let handle = set_timeout_with_handle(
|
||||
let handle = set_timeout(
|
||||
move || {
|
||||
refresh_token.dispatch(RefreshToken {
|
||||
email: email.get_untracked().unwrap(),
|
||||
|
||||
@@ -23,10 +23,11 @@ pin_project! {
|
||||
#[derive(Clone)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct ScopedFuture<Fut> {
|
||||
pub owner: Owner,
|
||||
pub observer: Option<AnySubscriber>,
|
||||
owner: Owner,
|
||||
observer: Option<AnySubscriber>,
|
||||
diagnostics: bool,
|
||||
#[pin]
|
||||
pub fut: Fut,
|
||||
fut: Fut,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +40,7 @@ impl<Fut> ScopedFuture<Fut> {
|
||||
Self {
|
||||
owner,
|
||||
observer,
|
||||
diagnostics: true,
|
||||
fut,
|
||||
}
|
||||
}
|
||||
@@ -51,19 +53,19 @@ impl<Fut> ScopedFuture<Fut> {
|
||||
Self {
|
||||
owner,
|
||||
observer: None,
|
||||
diagnostics: false,
|
||||
fut,
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[track_caller]
|
||||
pub fn new_untracked_with_diagnostics(
|
||||
fut: Fut,
|
||||
) -> ScopedFutureUntrackedWithDiagnostics<Fut> {
|
||||
pub fn new_untracked_with_diagnostics(fut: Fut) -> Self {
|
||||
let owner = Owner::current().unwrap_or_default();
|
||||
ScopedFutureUntrackedWithDiagnostics {
|
||||
Self {
|
||||
owner,
|
||||
observer: None,
|
||||
diagnostics: true,
|
||||
fut,
|
||||
}
|
||||
}
|
||||
@@ -75,41 +77,19 @@ impl<Fut: Future> Future for ScopedFuture<Fut> {
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
this.owner.with(|| {
|
||||
#[cfg(debug_assertions)]
|
||||
let _maybe_guard = if this.observer.is_none() {
|
||||
Some(crate::diagnostics::SpecialNonReactiveZone::enter())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
this.observer.with_observer(|| this.fut.poll(cx))
|
||||
this.observer.with_observer(|| {
|
||||
#[cfg(debug_assertions)]
|
||||
let _maybe_guard = if *this.diagnostics {
|
||||
None
|
||||
} else {
|
||||
Some(crate::diagnostics::SpecialNonReactiveZone::enter())
|
||||
};
|
||||
this.fut.poll(cx)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// A [`Future`] wrapper that sets the [`Owner`] and [`Observer`] before polling the inner
|
||||
/// `Future`, output of [`ScopedFuture::new_untracked_with_diagnostics`].
|
||||
///
|
||||
/// In leptos 0.9 this will be replaced with `ScopedFuture` itself.
|
||||
#[derive(Clone)]
|
||||
pub struct ScopedFutureUntrackedWithDiagnostics<Fut> {
|
||||
owner: Owner,
|
||||
observer: Option<AnySubscriber>,
|
||||
#[pin]
|
||||
fut: Fut,
|
||||
}
|
||||
}
|
||||
|
||||
impl<Fut: Future> Future for ScopedFutureUntrackedWithDiagnostics<Fut> {
|
||||
type Output = Fut::Output;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
this.owner
|
||||
.with(|| this.observer.with_observer(|| this.fut.poll(cx)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Utilities used to track whether asynchronous computeds are currently loading.
|
||||
pub mod suspense {
|
||||
use crate::{
|
||||
|
||||
@@ -34,14 +34,28 @@ mod tests {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
let _: Signal<usize> = (|| 2).into_reactive_value();
|
||||
let _: Signal<usize, LocalStorage> = 2.into_reactive_value();
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
let _: Signal<usize, LocalStorage> = (|| 2).into_reactive_value();
|
||||
let _: Signal<String> = "str".into_reactive_value();
|
||||
let _: Signal<String, LocalStorage> = "str".into_reactive_value();
|
||||
|
||||
// Confirm doesn't affect nightly function syntax:
|
||||
#[cfg(all(rustc_nightly, feature = "nightly"))]
|
||||
{
|
||||
let sig: Signal<usize> = Signal::stored(2).into_reactive_value();
|
||||
assert_eq!(sig(), 2);
|
||||
}
|
||||
|
||||
// Confirm can be used in more complex expressions:
|
||||
{
|
||||
use crate::traits::Get;
|
||||
let a: Signal<usize> = (|| 2).into_reactive_value();
|
||||
let b: Signal<usize> = Signal::stored(2).into_reactive_value();
|
||||
let _: Signal<usize> =
|
||||
(move || a.get() + b.get()).into_reactive_value();
|
||||
}
|
||||
|
||||
#[derive(TypedBuilder)]
|
||||
struct Foo {
|
||||
#[builder(setter(
|
||||
@@ -53,7 +67,6 @@ mod tests {
|
||||
}
|
||||
|
||||
assert_eq!(Foo::builder().sig(2).build().sig.get_untracked(), 2);
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
assert_eq!(Foo::builder().sig(|| 2).build().sig.get_untracked(), 2);
|
||||
assert_eq!(
|
||||
Foo::builder()
|
||||
|
||||
@@ -145,16 +145,90 @@ macro_rules! impl_get_fn_traits_get_arena {
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_get_fn_traits_get_arena_with_readable_deref_impl {
|
||||
($($ty:ident),*) => {
|
||||
$(
|
||||
/// Allow calling $ty() syntax
|
||||
impl<T: Clone + 'static, S: Storage<T> + 'static> std::ops::Deref
|
||||
for $ty<T, S>
|
||||
where
|
||||
$ty<T, S>: crate::traits::Get<Value = T>,
|
||||
$ty<T, S>: Get, S: Storage<T> + Storage<Option<T>> + Storage<SignalTypes<Option<T>, S>>
|
||||
{
|
||||
type Target = dyn Fn() -> T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { readable_deref_impl::ReadableDerefImpl::deref_impl(self) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + 'static, S: Storage<T> + 'static> readable_deref_impl::ReadableDerefImpl for $ty<T, S>
|
||||
where
|
||||
$ty<T, S>: crate::traits::Get<Value = T>,
|
||||
$ty<T, S>: Get, S: Storage<T> + Storage<Option<T>> + Storage<SignalTypes<Option<T>, S>>
|
||||
{
|
||||
}
|
||||
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_get_fn_traits_get![ArcReadSignal, ArcRwSignal];
|
||||
impl_get_fn_traits_get_arena![
|
||||
ReadSignal,
|
||||
RwSignal,
|
||||
ArcMemo,
|
||||
ArcSignal,
|
||||
Signal,
|
||||
MaybeSignal,
|
||||
Memo,
|
||||
MaybeProp
|
||||
];
|
||||
impl_get_fn_traits_get_arena_with_readable_deref_impl![ArcSignal, Signal];
|
||||
impl_set_fn_traits![ArcRwSignal, ArcWriteSignal];
|
||||
impl_set_fn_traits_arena![RwSignal, WriteSignal, SignalSetter];
|
||||
|
||||
mod readable_deref_impl {
|
||||
/// Derived from the implementation and original creaters at dioxus
|
||||
/// https://docs.rs/dioxus-signals/0.6.3/src/dioxus_signals/signal.rs.html#485-494
|
||||
pub trait ReadableDerefImpl: crate::traits::Get {
|
||||
/// SAFETY: You must call this function directly with `self` as the argument.
|
||||
/// This function relies on the size of the object you return from the deref
|
||||
/// being the same as the object you pass in
|
||||
#[doc(hidden)]
|
||||
unsafe fn deref_impl<'a>(&self) -> &'a dyn Fn() -> Self::Value
|
||||
where
|
||||
Self: Sized + 'a,
|
||||
Self::Value: Clone + 'static,
|
||||
{
|
||||
// https://github.com/dtolnay/case-studies/tree/master/callable-types
|
||||
|
||||
// First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit<Self>).
|
||||
let uninit_callable = std::mem::MaybeUninit::<Self>::uninit();
|
||||
// Then move that value into the closure. We assume that the closure now has a in memory layout of Self.
|
||||
let uninit_closure =
|
||||
move || Self::get(unsafe { &*uninit_callable.as_ptr() });
|
||||
|
||||
// Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure.
|
||||
let size_of_closure = std::mem::size_of_val(&uninit_closure);
|
||||
assert_eq!(size_of_closure, std::mem::size_of::<Self>());
|
||||
|
||||
// Then cast the lifetime of the closure to the lifetime of &self.
|
||||
fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T {
|
||||
b
|
||||
}
|
||||
let reference_to_closure = cast_lifetime(
|
||||
{
|
||||
// The real closure that we will never use.
|
||||
&uninit_closure
|
||||
},
|
||||
#[allow(clippy::missing_transmute_annotations)]
|
||||
// We transmute self into a reference to the closure. This is safe because we know that the closure has the same memory layout as Self so &Closure == &Self.
|
||||
unsafe {
|
||||
std::mem::transmute(self)
|
||||
},
|
||||
);
|
||||
|
||||
// Cast the closure to a trait object.
|
||||
reference_to_closure as &_
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1118,17 +1118,13 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
#[doc(hidden)]
|
||||
pub struct __IntoReactiveValueMarkerSignalFromReactiveClosure;
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
#[doc(hidden)]
|
||||
pub struct __IntoReactiveValueMarkerSignalStrOutputToString;
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
#[doc(hidden)]
|
||||
pub struct __IntoReactiveValueMarkerOptionalSignalFromReactiveClosureAlways;
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
impl<T, F>
|
||||
crate::IntoReactiveValue<
|
||||
Signal<T, SyncStorage>,
|
||||
@@ -1143,7 +1139,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
impl<T, F>
|
||||
crate::IntoReactiveValue<
|
||||
ArcSignal<T, SyncStorage>,
|
||||
@@ -1158,7 +1153,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
impl<T, F>
|
||||
crate::IntoReactiveValue<
|
||||
Signal<T, LocalStorage>,
|
||||
@@ -1173,7 +1167,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
impl<T, F>
|
||||
crate::IntoReactiveValue<
|
||||
ArcSignal<T, LocalStorage>,
|
||||
@@ -1188,7 +1181,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
impl<F>
|
||||
crate::IntoReactiveValue<
|
||||
Signal<String, SyncStorage>,
|
||||
@@ -1202,7 +1194,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
impl<F>
|
||||
crate::IntoReactiveValue<
|
||||
ArcSignal<String, SyncStorage>,
|
||||
@@ -1216,7 +1207,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
impl<F>
|
||||
crate::IntoReactiveValue<
|
||||
Signal<String, LocalStorage>,
|
||||
@@ -1230,7 +1220,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
impl<F>
|
||||
crate::IntoReactiveValue<
|
||||
ArcSignal<String, LocalStorage>,
|
||||
@@ -1244,7 +1233,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
impl<T, F>
|
||||
crate::IntoReactiveValue<
|
||||
Signal<Option<T>, SyncStorage>,
|
||||
@@ -1259,7 +1247,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
impl<T, F>
|
||||
crate::IntoReactiveValue<
|
||||
ArcSignal<Option<T>, SyncStorage>,
|
||||
@@ -1274,7 +1261,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
impl<T, F>
|
||||
crate::IntoReactiveValue<
|
||||
Signal<Option<T>, LocalStorage>,
|
||||
@@ -1289,7 +1275,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
impl<T, F>
|
||||
crate::IntoReactiveValue<
|
||||
ArcSignal<Option<T>, LocalStorage>,
|
||||
|
||||
@@ -658,7 +658,7 @@ pub fn RoutingProgress(
|
||||
move |prev: Option<Option<IntervalHandle>>| {
|
||||
if is_routing.get() && !is_showing.get() {
|
||||
set_is_showing.set(true);
|
||||
set_interval_with_handle(
|
||||
set_interval(
|
||||
move || {
|
||||
set_progress.update(|n| *n += percent_per_increment);
|
||||
},
|
||||
@@ -670,7 +670,7 @@ pub fn RoutingProgress(
|
||||
prev?
|
||||
} else {
|
||||
set_progress.set(100.0);
|
||||
set_timeout(
|
||||
_ = set_timeout(
|
||||
move || {
|
||||
set_progress.set(0.0);
|
||||
set_is_showing.set(false);
|
||||
|
||||
@@ -131,14 +131,14 @@ where
|
||||
|
||||
if !IS_NAVIGATING.load(Ordering::Relaxed) {
|
||||
IS_NAVIGATING.store(true, Ordering::Relaxed);
|
||||
request_animation_frame({
|
||||
_ = request_animation_frame({
|
||||
let navigate = navigate.clone();
|
||||
let nav_options = nav_options.clone();
|
||||
move || {
|
||||
navigate(&new_url, nav_options.clone());
|
||||
IS_NAVIGATING.store(false, Ordering::Relaxed)
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -259,7 +259,7 @@ impl LocationProvider for BrowserUrl {
|
||||
if url.origin() == current_origin {
|
||||
let navigate = navigate.clone();
|
||||
// delay by a tick here, so that the Action updates *before* the redirect
|
||||
request_animation_frame(move || {
|
||||
_ = request_animation_frame(move || {
|
||||
navigate(&url.href(), Default::default());
|
||||
});
|
||||
// Use set_href() if the conditions for client-side navigation were not satisfied
|
||||
|
||||
@@ -25,6 +25,7 @@ send_wrapper = { features = [
|
||||
"futures",
|
||||
], optional = true, workspace = true, default-features = true }
|
||||
thiserror = { workspace = true, default-features = true }
|
||||
typed-builder = { workspace = true, default-features = true }
|
||||
|
||||
# registration system
|
||||
inventory = { optional = true, workspace = true, default-features = true }
|
||||
|
||||
@@ -141,7 +141,8 @@ pub mod browser {
|
||||
Err(OutputStreamError::from_server_fn_error(
|
||||
ServerFnErrorErr::Request(err.to_string()),
|
||||
)
|
||||
.ser())
|
||||
.ser()
|
||||
.body)
|
||||
}
|
||||
});
|
||||
let stream = SendWrapper::new(stream);
|
||||
@@ -281,7 +282,8 @@ pub mod reqwest {
|
||||
Err(e) => Err(OutputStreamError::from_server_fn_error(
|
||||
ServerFnErrorErr::Request(e.to_string()),
|
||||
)
|
||||
.ser()),
|
||||
.ser()
|
||||
.body),
|
||||
}),
|
||||
write.with(|msg: Bytes| async move {
|
||||
Ok::<
|
||||
|
||||
@@ -120,7 +120,7 @@ where
|
||||
async fn into_res(self) -> Result<Response, E> {
|
||||
Response::try_from_stream(
|
||||
Streaming::CONTENT_TYPE,
|
||||
self.into_inner().map_err(|e| e.ser()),
|
||||
self.into_inner().map_err(|e| e.ser().body),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -255,7 +255,7 @@ where
|
||||
Response::try_from_stream(
|
||||
Streaming::CONTENT_TYPE,
|
||||
self.into_inner()
|
||||
.map(|stream| stream.map(Into::into).map_err(|e| e.ser())),
|
||||
.map(|stream| stream.map(Into::into).map_err(|e| e.ser().body)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,8 @@ where
|
||||
{
|
||||
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)
|
||||
let args = serde_qs::Config::new()
|
||||
.use_form_encoding(true)
|
||||
.deserialize_str::<Self>(string_data)
|
||||
.map_err(|e| {
|
||||
ServerFnErrorErr::Args(e.to_string()).into_app_error()
|
||||
@@ -97,7 +98,8 @@ where
|
||||
{
|
||||
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)
|
||||
let args = serde_qs::Config::new()
|
||||
.use_form_encoding(true)
|
||||
.deserialize_str::<Self>(&string_data)
|
||||
.map_err(|e| {
|
||||
ServerFnErrorErr::Args(e.to_string()).into_app_error()
|
||||
@@ -136,7 +138,8 @@ where
|
||||
{
|
||||
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)
|
||||
let args = serde_qs::Config::new()
|
||||
.use_form_encoding(true)
|
||||
.deserialize_str::<Self>(string_data)
|
||||
.map_err(|e| {
|
||||
ServerFnErrorErr::Args(e.to_string()).into_app_error()
|
||||
@@ -175,7 +178,8 @@ where
|
||||
{
|
||||
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)
|
||||
let args = serde_qs::Config::new()
|
||||
.use_form_encoding(true)
|
||||
.deserialize_str::<Self>(&string_data)
|
||||
.map_err(|e| {
|
||||
ServerFnErrorErr::Args(e.to_string()).into_app_error()
|
||||
@@ -214,7 +218,8 @@ where
|
||||
{
|
||||
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)
|
||||
let args = serde_qs::Config::new()
|
||||
.use_form_encoding(true)
|
||||
.deserialize_str::<Self>(&string_data)
|
||||
.map_err(|e| {
|
||||
ServerFnErrorErr::Args(e.to_string()).into_app_error()
|
||||
|
||||
@@ -9,6 +9,7 @@ use std::{
|
||||
str::FromStr,
|
||||
};
|
||||
use throw_error::Error;
|
||||
use typed_builder::TypedBuilder;
|
||||
use url::Url;
|
||||
|
||||
/// A custom header that can be used to indicate a server function returned an error.
|
||||
@@ -474,7 +475,7 @@ impl<E: FromServerFnError> ServerFnUrlError<E> {
|
||||
let mut url = Url::parse(base)?;
|
||||
url.query_pairs_mut()
|
||||
.append_pair("__path", &self.path)
|
||||
.append_pair("__err", &URL_SAFE.encode(self.error.ser()));
|
||||
.append_pair("__err", &URL_SAFE.encode(self.error.ser().body));
|
||||
Ok(url)
|
||||
}
|
||||
|
||||
@@ -536,7 +537,7 @@ impl<E: FromServerFnError> Display for ServerFnErrorWrapper<E> {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
<E::Encoder as FormatType>::into_encoded_string(self.0.ser())
|
||||
<E::Encoder as FormatType>::into_encoded_string(self.0.ser().body)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -560,6 +561,17 @@ impl<E: FromServerFnError> FromStr for ServerFnErrorWrapper<E> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Response parts returned by [`FromServerFnError::ser`] to be returned to the client.
|
||||
#[derive(TypedBuilder)]
|
||||
#[non_exhaustive]
|
||||
pub struct ServerFnErrorResponseParts {
|
||||
/// The raw [`Bytes`] of the serialized error.
|
||||
pub body: Bytes,
|
||||
/// The value of the `CONTENT_TYPE` associated constant for the `FromServerFnError`
|
||||
/// implementation. Used to set the `content-type` header in http responses.
|
||||
pub content_type: &'static str,
|
||||
}
|
||||
|
||||
/// A trait for types that can be returned from a server function.
|
||||
pub trait FromServerFnError: std::fmt::Debug + Sized + 'static {
|
||||
/// The encoding strategy used to serialize and deserialize this error type. Must implement the [`Encodes`](server_fn::Encodes) trait for references to the error type.
|
||||
@@ -568,9 +580,10 @@ pub trait FromServerFnError: std::fmt::Debug + Sized + 'static {
|
||||
/// Converts a [`ServerFnErrorErr`] into the application-specific custom error type.
|
||||
fn from_server_fn_error(value: ServerFnErrorErr) -> Self;
|
||||
|
||||
/// Serializes the custom error type to bytes, according to the encoding given by `Self::Encoding`.
|
||||
fn ser(&self) -> Bytes {
|
||||
Self::Encoder::encode(self).unwrap_or_else(|e| {
|
||||
/// Converts the custom error type to [`ServerFnErrorResponseParts`], according to the encoding
|
||||
/// given by [`Self::Encoder`].
|
||||
fn ser(&self) -> ServerFnErrorResponseParts {
|
||||
let body = Self::Encoder::encode(self).unwrap_or_else(|e| {
|
||||
Self::Encoder::encode(&Self::from_server_fn_error(
|
||||
ServerFnErrorErr::Serialization(e.to_string()),
|
||||
))
|
||||
@@ -578,7 +591,11 @@ pub trait FromServerFnError: std::fmt::Debug + Sized + 'static {
|
||||
"error serializing should success at least with the \
|
||||
Serialization error",
|
||||
)
|
||||
})
|
||||
});
|
||||
ServerFnErrorResponseParts::builder()
|
||||
.body(body)
|
||||
.content_type(Self::Encoder::CONTENT_TYPE)
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Deserializes the custom error type, according to the encoding given by `Self::Encoding`.
|
||||
|
||||
@@ -309,18 +309,16 @@ pub trait ServerFn: Send + Sized {
|
||||
.await
|
||||
.map(|res| (res, None))
|
||||
.unwrap_or_else(|e| {
|
||||
let mut response =
|
||||
(
|
||||
<<Self as ServerFn>::Server as crate::Server<
|
||||
Self::Error,
|
||||
Self::InputStreamError,
|
||||
Self::OutputStreamError,
|
||||
>>::Response::error_response(
|
||||
Self::PATH, e.ser()
|
||||
);
|
||||
let content_type =
|
||||
<Self::Error as FromServerFnError>::Encoder::CONTENT_TYPE;
|
||||
response.content_type(content_type);
|
||||
(response, Some(e))
|
||||
),
|
||||
Some(e),
|
||||
)
|
||||
});
|
||||
|
||||
// if it accepts HTML, we'll redirect to the Referer
|
||||
@@ -671,8 +669,9 @@ where
|
||||
ServerFnErrorErr::Serialization(e.to_string()),
|
||||
)
|
||||
.ser()
|
||||
.body
|
||||
}),
|
||||
Err(err) => Err(err.ser()),
|
||||
Err(err) => Err(err.ser().body),
|
||||
};
|
||||
serialize_result(result)
|
||||
});
|
||||
@@ -715,9 +714,10 @@ where
|
||||
),
|
||||
)
|
||||
.ser()
|
||||
.body
|
||||
})
|
||||
}
|
||||
Err(err) => Err(err.ser()),
|
||||
Err(err) => Err(err.ser().body),
|
||||
};
|
||||
let result = serialize_result(result);
|
||||
if sink.send(result).await.is_err() {
|
||||
@@ -785,7 +785,8 @@ fn deserialize_result<E: FromServerFnError>(
|
||||
return Err(E::from_server_fn_error(
|
||||
ServerFnErrorErr::Deserialization("Data is empty".into()),
|
||||
)
|
||||
.ser());
|
||||
.ser()
|
||||
.body);
|
||||
}
|
||||
|
||||
let tag = bytes[0];
|
||||
@@ -797,7 +798,8 @@ fn deserialize_result<E: FromServerFnError>(
|
||||
_ => Err(E::from_server_fn_error(ServerFnErrorErr::Deserialization(
|
||||
"Invalid data tag".into(),
|
||||
))
|
||||
.ser()), // Invalid tag
|
||||
.ser()
|
||||
.body), // Invalid tag
|
||||
}
|
||||
}
|
||||
|
||||
@@ -887,7 +889,7 @@ 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) -> Bytes,
|
||||
ser: middleware::ServerFnErrorSerializer,
|
||||
}
|
||||
|
||||
impl<Req, Res> ServerFnTraitObj<Req, Res> {
|
||||
@@ -963,7 +965,7 @@ where
|
||||
fn run(
|
||||
&mut self,
|
||||
req: Req,
|
||||
_ser: fn(ServerFnErrorErr) -> Bytes,
|
||||
_err_ser: middleware::ServerFnErrorSerializer,
|
||||
) -> Pin<Box<dyn Future<Output = Res> + Send>> {
|
||||
let handler = self.handler;
|
||||
Box::pin(async move { handler(req).await })
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::error::ServerFnErrorErr;
|
||||
use bytes::Bytes;
|
||||
use crate::error::{ServerFnErrorErr, ServerFnErrorResponseParts};
|
||||
use std::{future::Future, pin::Pin};
|
||||
|
||||
/// An abstraction over a middleware layer, which can be used to add additional
|
||||
@@ -10,9 +9,10 @@ pub trait Layer<Req, Res>: Send + Sync + 'static {
|
||||
}
|
||||
|
||||
/// A type-erased service, which takes an HTTP request and returns a response.
|
||||
#[non_exhaustive]
|
||||
pub struct BoxedService<Req, Res> {
|
||||
/// A function that converts a [`ServerFnErrorErr`] into a string.
|
||||
pub ser: fn(ServerFnErrorErr) -> Bytes,
|
||||
/// A function that converts a [`ServerFnErrorErr`] into [`ServerFnErrorResponseParts`].
|
||||
pub err_ser: ServerFnErrorSerializer,
|
||||
/// The inner service.
|
||||
pub service: Box<dyn Service<Req, Res> + Send>,
|
||||
}
|
||||
@@ -20,11 +20,11 @@ pub struct BoxedService<Req, Res> {
|
||||
impl<Req, Res> BoxedService<Req, Res> {
|
||||
/// Constructs a type-erased service from this service.
|
||||
pub fn new(
|
||||
ser: fn(ServerFnErrorErr) -> Bytes,
|
||||
ser: ServerFnErrorSerializer,
|
||||
service: impl Service<Req, Res> + Send + 'static,
|
||||
) -> Self {
|
||||
Self {
|
||||
ser,
|
||||
err_ser: ser,
|
||||
service: Box::new(service),
|
||||
}
|
||||
}
|
||||
@@ -34,26 +34,30 @@ impl<Req, Res> BoxedService<Req, Res> {
|
||||
&mut self,
|
||||
req: Req,
|
||||
) -> Pin<Box<dyn Future<Output = Res> + Send>> {
|
||||
self.service.run(req, self.ser)
|
||||
self.service.run(req, self.err_ser)
|
||||
}
|
||||
}
|
||||
|
||||
/// Type alias for a function that serializes a [`ServerFnErrorErr`] into
|
||||
/// [`ServerFnErrorResponseParts`].
|
||||
pub type ServerFnErrorSerializer =
|
||||
fn(ServerFnErrorErr) -> ServerFnErrorResponseParts;
|
||||
|
||||
/// A service converts an HTTP request into a response.
|
||||
pub trait Service<Request, Response> {
|
||||
/// Converts a request into a response.
|
||||
fn run(
|
||||
&mut self,
|
||||
req: Request,
|
||||
ser: fn(ServerFnErrorErr) -> Bytes,
|
||||
err_ser: ServerFnErrorSerializer,
|
||||
) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "axum-no-default")]
|
||||
mod axum {
|
||||
use super::{BoxedService, Service};
|
||||
use super::{BoxedService, ServerFnErrorSerializer, Service};
|
||||
use crate::{error::ServerFnErrorErr, response::Res, ServerFnError};
|
||||
use axum::body::Body;
|
||||
use bytes::Bytes;
|
||||
use http::{Request, Response};
|
||||
use std::{future::Future, pin::Pin};
|
||||
|
||||
@@ -66,18 +70,15 @@ mod axum {
|
||||
fn run(
|
||||
&mut self,
|
||||
req: Request<Body>,
|
||||
ser: fn(ServerFnErrorErr) -> Bytes,
|
||||
err_ser: ServerFnErrorSerializer,
|
||||
) -> 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| {
|
||||
// TODO: This does not set the Content-Type on the response. Doing so will
|
||||
// require a breaking change in order to get the correct encoding from the
|
||||
// error's `FromServerFnError::Encoder::CONTENT_TYPE` impl.
|
||||
// Note: This only applies to middleware errors.
|
||||
let err =
|
||||
ser(ServerFnErrorErr::MiddlewareError(e.to_string()));
|
||||
let err = err_ser(ServerFnErrorErr::MiddlewareError(
|
||||
e.to_string(),
|
||||
));
|
||||
Response::<Body>::error_response(&path, err)
|
||||
})
|
||||
})
|
||||
@@ -105,7 +106,7 @@ mod axum {
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Request<Body>) -> Self::Future {
|
||||
let inner = self.service.run(req, self.ser);
|
||||
let inner = self.service.run(req, self.err_ser);
|
||||
Box::pin(async move { Ok(inner.await) })
|
||||
}
|
||||
}
|
||||
@@ -122,7 +123,7 @@ mod axum {
|
||||
&self,
|
||||
inner: BoxedService<Request<Body>, Response<Body>>,
|
||||
) -> BoxedService<Request<Body>, Response<Body>> {
|
||||
BoxedService::new(inner.ser, self.layer(inner))
|
||||
BoxedService::new(inner.err_ser, self.layer(inner))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,11 +132,11 @@ mod axum {
|
||||
mod actix {
|
||||
use crate::{
|
||||
error::ServerFnErrorErr,
|
||||
middleware::ServerFnErrorSerializer,
|
||||
request::actix::ActixRequest,
|
||||
response::{actix::ActixResponse, Res},
|
||||
};
|
||||
use actix_web::{HttpRequest, HttpResponse};
|
||||
use bytes::Bytes;
|
||||
use std::{future::Future, pin::Pin};
|
||||
|
||||
impl<S> super::Service<HttpRequest, HttpResponse> for S
|
||||
@@ -147,18 +148,15 @@ mod actix {
|
||||
fn run(
|
||||
&mut self,
|
||||
req: HttpRequest,
|
||||
ser: fn(ServerFnErrorErr) -> Bytes,
|
||||
err_ser: ServerFnErrorSerializer,
|
||||
) -> 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| {
|
||||
// TODO: This does not set the Content-Type on the response. Doing so will
|
||||
// require a breaking change in order to get the correct encoding from the
|
||||
// error's `FromServerFnError::Encoder::CONTENT_TYPE` impl.
|
||||
// Note: This only applies to middleware errors.
|
||||
let err =
|
||||
ser(ServerFnErrorErr::MiddlewareError(e.to_string()));
|
||||
let err = err_ser(ServerFnErrorErr::MiddlewareError(
|
||||
e.to_string(),
|
||||
));
|
||||
ActixResponse::error_response(&path, err).take()
|
||||
})
|
||||
})
|
||||
@@ -174,14 +172,15 @@ mod actix {
|
||||
fn run(
|
||||
&mut self,
|
||||
req: ActixRequest,
|
||||
ser: fn(ServerFnErrorErr) -> Bytes,
|
||||
err_ser: ServerFnErrorSerializer,
|
||||
) -> 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 =
|
||||
ser(ServerFnErrorErr::MiddlewareError(e.to_string()));
|
||||
let err = err_ser(ServerFnErrorErr::MiddlewareError(
|
||||
e.to_string(),
|
||||
));
|
||||
ActixResponse::error_response(&path, err).take()
|
||||
}))
|
||||
})
|
||||
|
||||
@@ -107,6 +107,7 @@ where
|
||||
e.to_string(),
|
||||
))
|
||||
.ser()
|
||||
.body
|
||||
})
|
||||
});
|
||||
Ok(SendWrapper::new(stream))
|
||||
@@ -143,7 +144,7 @@ where
|
||||
break;
|
||||
};
|
||||
if let Err(err) = session.binary(incoming).await {
|
||||
_ = response_stream_tx.start_send(Err(InputStreamError::from_server_fn_error(ServerFnErrorErr::Request(err.to_string())).ser()));
|
||||
_ = response_stream_tx.start_send(Err(InputStreamError::from_server_fn_error(ServerFnErrorErr::Request(err.to_string())).ser().body));
|
||||
}
|
||||
},
|
||||
outgoing = msg_stream.next().fuse() => {
|
||||
@@ -171,7 +172,7 @@ where
|
||||
Ok(_other) => {
|
||||
}
|
||||
Err(e) => {
|
||||
_ = response_stream_tx.start_send(Err(InputStreamError::from_server_fn_error(ServerFnErrorErr::Response(e.to_string())).ser()));
|
||||
_ = response_stream_tx.start_send(Err(InputStreamError::from_server_fn_error(ServerFnErrorErr::Response(e.to_string())).ser().body));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ where
|
||||
e.to_string(),
|
||||
))
|
||||
.ser()
|
||||
.body
|
||||
})
|
||||
}))
|
||||
}
|
||||
@@ -124,7 +125,7 @@ where
|
||||
.on_failed_upgrade({
|
||||
let mut outgoing_tx = outgoing_tx.clone();
|
||||
move |err: axum::Error| {
|
||||
_ = outgoing_tx.start_send(Err(InputStreamError::from_server_fn_error(ServerFnErrorErr::Response(err.to_string())).ser()));
|
||||
_ = outgoing_tx.start_send(Err(InputStreamError::from_server_fn_error(ServerFnErrorErr::Response(err.to_string())).ser().body));
|
||||
}
|
||||
})
|
||||
.on_upgrade(|mut session| async move {
|
||||
@@ -135,7 +136,7 @@ where
|
||||
break;
|
||||
};
|
||||
if let Err(err) = session.send(Message::Binary(incoming)).await {
|
||||
_ = outgoing_tx.start_send(Err(InputStreamError::from_server_fn_error(ServerFnErrorErr::Request(err.to_string())).ser()));
|
||||
_ = outgoing_tx.start_send(Err(InputStreamError::from_server_fn_error(ServerFnErrorErr::Request(err.to_string())).ser().body));
|
||||
}
|
||||
},
|
||||
outgoing = session.recv().fuse() => {
|
||||
@@ -159,7 +160,7 @@ where
|
||||
}
|
||||
Ok(_other) => {}
|
||||
Err(e) => {
|
||||
_ = outgoing_tx.start_send(Err(InputStreamError::from_server_fn_error(ServerFnErrorErr::Response(e.to_string())).ser()));
|
||||
_ = outgoing_tx.start_send(Err(InputStreamError::from_server_fn_error(ServerFnErrorErr::Response(e.to_string())).ser().body));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::{Res, TryRes};
|
||||
use crate::error::{
|
||||
FromServerFnError, ServerFnErrorWrapper, SERVER_FN_ERROR_HEADER,
|
||||
FromServerFnError, ServerFnErrorResponseParts, ServerFnErrorWrapper,
|
||||
SERVER_FN_ERROR_HEADER,
|
||||
};
|
||||
use actix_web::{
|
||||
http::{
|
||||
@@ -72,20 +73,15 @@ where
|
||||
}
|
||||
|
||||
impl Res for ActixResponse {
|
||||
fn error_response(path: &str, err: Bytes) -> Self {
|
||||
fn error_response(path: &str, err: ServerFnErrorResponseParts) -> Self {
|
||||
ActixResponse(SendWrapper::new(
|
||||
HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.append_header((SERVER_FN_ERROR_HEADER, path))
|
||||
.body(err),
|
||||
.append_header((CONTENT_TYPE, err.content_type))
|
||||
.body(err.body),
|
||||
))
|
||||
}
|
||||
|
||||
fn content_type(&mut self, content_type: &str) {
|
||||
if let Ok(content_type) = HeaderValue::from_str(content_type) {
|
||||
self.0.headers_mut().insert(CONTENT_TYPE, content_type);
|
||||
}
|
||||
}
|
||||
|
||||
fn redirect(&mut self, path: &str) {
|
||||
if let Ok(path) = HeaderValue::from_str(path) {
|
||||
*self.0.status_mut() = StatusCode::FOUND;
|
||||
|
||||
@@ -69,7 +69,8 @@ impl<E: FromServerFnError> ClientRes<E> for BrowserResponse {
|
||||
Err(E::from_server_fn_error(ServerFnErrorErr::Request(
|
||||
format!("{e:?}"),
|
||||
))
|
||||
.ser())
|
||||
.ser()
|
||||
.body)
|
||||
}
|
||||
Ok(data) => {
|
||||
let data = data.unchecked_into::<Uint8Array>();
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
|
||||
use super::{Res, TryRes};
|
||||
use crate::error::{
|
||||
FromServerFnError, IntoAppError, ServerFnErrorErr, ServerFnErrorWrapper,
|
||||
SERVER_FN_ERROR_HEADER,
|
||||
FromServerFnError, IntoAppError, ServerFnErrorErr,
|
||||
ServerFnErrorResponseParts, ServerFnErrorWrapper, SERVER_FN_ERROR_HEADER,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use futures::{Stream, TryStreamExt};
|
||||
@@ -92,21 +92,15 @@ where
|
||||
}
|
||||
|
||||
impl Res for Response<Body> {
|
||||
fn error_response(path: &str, err: Bytes) -> Self {
|
||||
fn error_response(path: &str, err: ServerFnErrorResponseParts) -> Self {
|
||||
Response::builder()
|
||||
.status(http::StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.header(SERVER_FN_ERROR_HEADER, path)
|
||||
.body(err.into())
|
||||
.header(header::CONTENT_TYPE, err.content_type)
|
||||
.body(err.body.into())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn content_type(&mut self, content_type: &str) {
|
||||
if let Ok(content_type) = HeaderValue::from_str(content_type) {
|
||||
self.headers_mut()
|
||||
.insert(header::CONTENT_TYPE, content_type);
|
||||
}
|
||||
}
|
||||
|
||||
fn redirect(&mut self, path: &str) {
|
||||
if let Ok(path) = HeaderValue::from_str(path) {
|
||||
self.headers_mut().insert(header::LOCATION, path);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::{Res, TryRes};
|
||||
use crate::error::{
|
||||
FromServerFnError, IntoAppError, ServerFnErrorErr, ServerFnErrorWrapper,
|
||||
SERVER_FN_ERROR_HEADER,
|
||||
FromServerFnError, IntoAppError, ServerFnErrorErr,
|
||||
ServerFnErrorResponseParts, ServerFnErrorWrapper, SERVER_FN_ERROR_HEADER,
|
||||
};
|
||||
use axum::body::Body;
|
||||
use bytes::Bytes;
|
||||
@@ -16,7 +16,7 @@ where
|
||||
let builder = http::Response::builder();
|
||||
builder
|
||||
.status(200)
|
||||
.header(http::header::CONTENT_TYPE, content_type)
|
||||
.header(header::CONTENT_TYPE, content_type)
|
||||
.body(Body::from(data))
|
||||
.map_err(|e| {
|
||||
ServerFnErrorErr::Response(e.to_string()).into_app_error()
|
||||
@@ -27,7 +27,7 @@ where
|
||||
let builder = http::Response::builder();
|
||||
builder
|
||||
.status(200)
|
||||
.header(http::header::CONTENT_TYPE, content_type)
|
||||
.header(header::CONTENT_TYPE, content_type)
|
||||
.body(Body::from(data))
|
||||
.map_err(|e| {
|
||||
ServerFnErrorErr::Response(e.to_string()).into_app_error()
|
||||
@@ -43,7 +43,7 @@ where
|
||||
let builder = http::Response::builder();
|
||||
builder
|
||||
.status(200)
|
||||
.header(http::header::CONTENT_TYPE, content_type)
|
||||
.header(header::CONTENT_TYPE, content_type)
|
||||
.body(body)
|
||||
.map_err(|e| {
|
||||
ServerFnErrorErr::Response(e.to_string()).into_app_error()
|
||||
@@ -52,21 +52,15 @@ where
|
||||
}
|
||||
|
||||
impl Res for Response<Body> {
|
||||
fn error_response(path: &str, err: Bytes) -> Self {
|
||||
fn error_response(path: &str, err: ServerFnErrorResponseParts) -> Self {
|
||||
Response::builder()
|
||||
.status(http::StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.header(SERVER_FN_ERROR_HEADER, path)
|
||||
.body(err.into())
|
||||
.header(header::CONTENT_TYPE, err.content_type)
|
||||
.body(err.body.into())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn content_type(&mut self, content_type: &str) {
|
||||
if let Ok(content_type) = HeaderValue::from_str(content_type) {
|
||||
self.headers_mut()
|
||||
.insert(header::CONTENT_TYPE, content_type);
|
||||
}
|
||||
}
|
||||
|
||||
fn redirect(&mut self, path: &str) {
|
||||
if let Ok(path) = HeaderValue::from_str(path) {
|
||||
self.headers_mut().insert(header::LOCATION, path);
|
||||
|
||||
@@ -13,6 +13,7 @@ pub mod http;
|
||||
#[cfg(feature = "reqwest")]
|
||||
pub mod reqwest;
|
||||
|
||||
use crate::error::ServerFnErrorResponseParts;
|
||||
use bytes::Bytes;
|
||||
use futures::Stream;
|
||||
use std::future::Future;
|
||||
@@ -37,14 +38,8 @@ where
|
||||
|
||||
/// 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 as its body.
|
||||
fn error_response(path: &str, err: Bytes) -> Self;
|
||||
/// Set the `Content-Type` header for the response.
|
||||
fn content_type(&mut self, #[allow(unused_variables)] content_type: &str) {
|
||||
// TODO 0.9: remove this method and default implementation. It is only included here
|
||||
// to allow setting the `Content-Type` header for error responses without requiring a
|
||||
// semver-incompatible change.
|
||||
}
|
||||
/// Converts an error into a response, with a `500` status code and the serialized error as its body.
|
||||
fn error_response(path: &str, err: ServerFnErrorResponseParts) -> Self;
|
||||
/// Redirect the response by setting a 302 code and Location header.
|
||||
fn redirect(&mut self, path: &str);
|
||||
}
|
||||
@@ -104,11 +99,7 @@ impl<E> TryRes<E> for BrowserMockRes {
|
||||
}
|
||||
|
||||
impl Res for BrowserMockRes {
|
||||
fn error_response(_path: &str, _err: Bytes) -> Self {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn content_type(&mut self, _content_type: &str) {
|
||||
fn error_response(_path: &str, _err: ServerFnErrorResponseParts) -> Self {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ impl<E: FromServerFnError> ClientRes<E> for Response {
|
||||
Ok(self.bytes_stream().map_err(|e| {
|
||||
E::from_server_fn_error(ServerFnErrorErr::Response(e.to_string()))
|
||||
.ser()
|
||||
.body
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ web-sys = { features = [
|
||||
"SecurityPolicyViolationEvent",
|
||||
"StorageEvent",
|
||||
"SubmitEvent",
|
||||
"ToggleEvent",
|
||||
"TouchEvent",
|
||||
"TransitionEvent",
|
||||
"UiEvent",
|
||||
@@ -179,6 +180,7 @@ default = ["testing"]
|
||||
delegation = [] # enables event delegation
|
||||
error-hook = []
|
||||
hydrate = []
|
||||
lazy = []
|
||||
islands = ["dep:serde", "dep:serde_json"]
|
||||
ssr = []
|
||||
oco = ["dep:oco_ref"]
|
||||
|
||||
@@ -608,7 +608,7 @@ generate_event_types! {
|
||||
animation start: AnimationEvent,
|
||||
aux click: MouseEvent,
|
||||
before input: InputEvent,
|
||||
before toggle: Event, // web_sys does not include `ToggleEvent`
|
||||
before toggle: ToggleEvent,
|
||||
#[does_not_bubble]
|
||||
blur: FocusEvent,
|
||||
#[does_not_bubble]
|
||||
@@ -719,7 +719,7 @@ generate_event_types! {
|
||||
#[does_not_bubble]
|
||||
time update: Event,
|
||||
#[does_not_bubble]
|
||||
toggle: Event,
|
||||
toggle: ToggleEvent,
|
||||
touch cancel: TouchEvent,
|
||||
touch end: TouchEvent,
|
||||
touch move: TouchEvent,
|
||||
|
||||
@@ -17,7 +17,7 @@ use crate::{
|
||||
};
|
||||
use futures::future::{join, join_all};
|
||||
use std::{any::TypeId, fmt::Debug};
|
||||
#[cfg(any(feature = "ssr", feature = "hydrate"))]
|
||||
#[cfg(any(feature = "ssr", all(feature = "hydrate", feature = "lazy")))]
|
||||
use std::{future::Future, pin::Pin};
|
||||
|
||||
/// A type-erased view. This can be used if control flow requires that multiple different types of
|
||||
@@ -67,10 +67,10 @@ pub struct AnyView {
|
||||
resolve: fn(Erased) -> Pin<Box<dyn Future<Output = AnyView> + Send>>,
|
||||
#[cfg(feature = "ssr")]
|
||||
dry_resolve: fn(&mut Erased),
|
||||
#[cfg(feature = "hydrate")]
|
||||
#[cfg(all(feature = "hydrate", not(feature = "lazy")))]
|
||||
#[allow(clippy::type_complexity)]
|
||||
hydrate_from_server: fn(Erased, &Cursor, &PositionState) -> AnyViewState,
|
||||
#[cfg(feature = "hydrate")]
|
||||
#[cfg(all(feature = "hydrate", feature = "lazy"))]
|
||||
#[allow(clippy::type_complexity)]
|
||||
hydrate_async: fn(
|
||||
Erased,
|
||||
@@ -291,7 +291,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
#[cfg(all(feature = "hydrate", not(feature = "lazy")))]
|
||||
fn hydrate_from_server<T: RenderHtml + 'static>(
|
||||
value: Erased,
|
||||
cursor: &Cursor,
|
||||
@@ -313,7 +313,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
#[cfg(all(feature = "hydrate", feature = "lazy"))]
|
||||
fn hydrate_async<T: RenderHtml + 'static>(
|
||||
value: Erased,
|
||||
cursor: &Cursor,
|
||||
@@ -367,9 +367,9 @@ where
|
||||
to_html_async: to_html_async::<T::Owned>,
|
||||
#[cfg(feature = "ssr")]
|
||||
to_html_async_ooo: to_html_async_ooo::<T::Owned>,
|
||||
#[cfg(feature = "hydrate")]
|
||||
#[cfg(all(feature = "hydrate", not(feature = "lazy")))]
|
||||
hydrate_from_server: hydrate_from_server::<T::Owned>,
|
||||
#[cfg(feature = "hydrate")]
|
||||
#[cfg(all(feature = "hydrate", feature = "lazy"))]
|
||||
hydrate_async: hydrate_async::<T::Owned>,
|
||||
value: Erased::new(value),
|
||||
}
|
||||
@@ -572,7 +572,7 @@ impl RenderHtml for AnyView {
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
#[cfg(feature = "hydrate")]
|
||||
#[cfg(all(feature = "hydrate", not(feature = "lazy")))]
|
||||
{
|
||||
if FROM_SERVER {
|
||||
(self.hydrate_from_server)(self.value, cursor, position)
|
||||
@@ -583,6 +583,14 @@ impl RenderHtml for AnyView {
|
||||
);
|
||||
}
|
||||
}
|
||||
#[cfg(all(feature = "hydrate", feature = "lazy"))]
|
||||
{
|
||||
use futures::FutureExt;
|
||||
|
||||
(self.hydrate_async)(self.value, cursor, position)
|
||||
.now_or_never()
|
||||
.unwrap()
|
||||
}
|
||||
#[cfg(not(feature = "hydrate"))]
|
||||
{
|
||||
_ = cursor;
|
||||
@@ -599,12 +607,22 @@ impl RenderHtml for AnyView {
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
#[cfg(feature = "hydrate")]
|
||||
#[cfg(all(feature = "hydrate", feature = "lazy"))]
|
||||
{
|
||||
#[cfg(all(feature = "hydrate", feature = "lazy"))]
|
||||
let state =
|
||||
(self.hydrate_async)(self.value, cursor, position).await;
|
||||
state
|
||||
}
|
||||
#[cfg(all(feature = "hydrate", not(feature = "lazy")))]
|
||||
{
|
||||
_ = cursor;
|
||||
_ = position;
|
||||
panic!(
|
||||
"the `lazy` feature on `tachys` must be activated to use lazy \
|
||||
hydration"
|
||||
);
|
||||
}
|
||||
#[cfg(not(feature = "hydrate"))]
|
||||
{
|
||||
_ = cursor;
|
||||
|
||||
Reference in New Issue
Block a user