Compare commits

..

2 Commits
v0.8.8 ... 4209

Author SHA1 Message Date
Greg Johnston
e8d80e0733 fix: set Content-Type correctly on server function error responses (closes #4209) 2025-08-24 14:51:35 -04:00
Greg Johnston
0f2168b92c chore: fix outdated descriptions in FromServerFnError docs 2025-08-24 13:50:14 -04:00
40 changed files with 130 additions and 259 deletions

26
Cargo.lock generated
View File

@@ -1788,7 +1788,7 @@ checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
[[package]]
name = "leptos"
version = "0.8.8"
version = "0.8.6"
dependencies = [
"any_spawner",
"base64",
@@ -1869,7 +1869,7 @@ dependencies = [
[[package]]
name = "leptos_axum"
version = "0.8.6"
version = "0.8.5"
dependencies = [
"any_spawner",
"axum",
@@ -1892,7 +1892,7 @@ dependencies = [
[[package]]
name = "leptos_config"
version = "0.8.7"
version = "0.8.5"
dependencies = [
"config",
"regex",
@@ -1906,7 +1906,7 @@ dependencies = [
[[package]]
name = "leptos_dom"
version = "0.8.6"
version = "0.8.5"
dependencies = [
"js-sys",
"leptos",
@@ -1952,7 +1952,7 @@ dependencies = [
[[package]]
name = "leptos_macro"
version = "0.8.7"
version = "0.8.6"
dependencies = [
"attribute-derive",
"cfg-if",
@@ -1972,7 +1972,7 @@ dependencies = [
"rustc_version",
"serde",
"server_fn",
"server_fn_macro 0.8.7",
"server_fn_macro 0.8.6",
"syn 2.0.104",
"tracing",
"trybuild",
@@ -1996,7 +1996,7 @@ dependencies = [
[[package]]
name = "leptos_router"
version = "0.8.6"
version = "0.8.5"
dependencies = [
"any_spawner",
"either_of",
@@ -2746,7 +2746,7 @@ dependencies = [
[[package]]
name = "reactive_graph"
version = "0.2.6"
version = "0.2.5"
dependencies = [
"any_spawner",
"async-lock",
@@ -2789,7 +2789,7 @@ dependencies = [
[[package]]
name = "reactive_stores_macro"
version = "0.2.6"
version = "0.2.5"
dependencies = [
"convert_case 0.8.0",
"proc-macro-error2",
@@ -3283,7 +3283,7 @@ dependencies = [
[[package]]
name = "server_fn"
version = "0.8.6"
version = "0.8.5"
dependencies = [
"actix-web",
"actix-ws",
@@ -3346,7 +3346,7 @@ dependencies = [
[[package]]
name = "server_fn_macro"
version = "0.8.7"
version = "0.8.6"
dependencies = [
"const_format",
"convert_case 0.8.0",
@@ -3361,7 +3361,7 @@ dependencies = [
name = "server_fn_macro_default"
version = "0.8.5"
dependencies = [
"server_fn_macro 0.8.7",
"server_fn_macro 0.8.6",
"syn 2.0.104",
]
@@ -3573,7 +3573,7 @@ dependencies = [
[[package]]
name = "tachys"
version = "0.2.7"
version = "0.2.6"
dependencies = [
"any_spawner",
"async-trait",

View File

@@ -50,26 +50,26 @@ any_spawner = { path = "./any_spawner/", version = "0.3.0" }
const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1" }
either_of = { path = "./either_of/", version = "0.1.6" }
hydration_context = { path = "./hydration_context", version = "0.3.0" }
leptos = { path = "./leptos", version = "0.8.8" }
leptos_config = { path = "./leptos_config", version = "0.8.7" }
leptos_dom = { path = "./leptos_dom", version = "0.8.6" }
leptos = { path = "./leptos", version = "0.8.6" }
leptos_config = { path = "./leptos_config", version = "0.8.5" }
leptos_dom = { path = "./leptos_dom", version = "0.8.5" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.8.5" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.8.5" }
leptos_macro = { path = "./leptos_macro", version = "0.8.7" }
leptos_router = { path = "./router", version = "0.8.6" }
leptos_macro = { path = "./leptos_macro", version = "0.8.6" }
leptos_router = { path = "./router", version = "0.8.5" }
leptos_router_macro = { path = "./router_macro", version = "0.8.5" }
leptos_server = { path = "./leptos_server", version = "0.8.5" }
leptos_meta = { path = "./meta", version = "0.8.5" }
next_tuple = { path = "./next_tuple", version = "0.1.0" }
oco_ref = { path = "./oco", version = "0.2.1" }
or_poisoned = { path = "./or_poisoned", version = "0.1.0" }
reactive_graph = { path = "./reactive_graph", version = "0.2.6" }
reactive_graph = { path = "./reactive_graph", version = "0.2.5" }
reactive_stores = { path = "./reactive_stores", version = "0.2.5" }
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.2.6" }
server_fn = { path = "./server_fn", version = "0.8.6" }
server_fn_macro = { path = "./server_fn_macro", version = "0.8.7" }
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.2.5" }
server_fn = { path = "./server_fn", version = "0.8.5" }
server_fn_macro = { path = "./server_fn_macro", version = "0.8.6" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.8.5" }
tachys = { path = "./tachys", version = "0.2.7" }
tachys = { path = "./tachys", version = "0.2.6" }
wasm_split_helpers = { path = "./wasm_split", version = "0.1.2" }
wasm_split_macros = { path = "./wasm_split_macros", version = "0.1.2" }

View File

@@ -1,7 +0,0 @@
@check_issue_4005
Feature: Check that issue 4005 does not reappear
Scenario: The second item is selected.
Given I see the app
And I can access regression test 4005
Then I see the value of select is 2

View File

@@ -1,9 +0,0 @@
@check_issue_4217
Feature: Check that issue 4217 does not reappear
Scenario: All items are selected.
Given I see the app
And I can access regression test 4217
Then I see option1 is selected
And I see option2 is selected
And I see option3 is selected

View File

@@ -18,28 +18,3 @@ pub async fn element_exists(client: &Client, id: &str) -> Result<()> {
.expect(&format!("could not find element with id `{id}`"));
Ok(())
}
pub async fn select_option_is_selected(
client: &Client,
id: &str,
) -> Result<()> {
let el = find::element_by_id(client, id)
.await
.expect(&format!("could not find element with id `{id}`"));
let selected = el.prop("selected").await?;
assert_eq!(selected.as_deref(), Some("true"));
Ok(())
}
pub async fn element_value_is(
client: &Client,
id: &str,
expected: &str,
) -> Result<()> {
let el = find::element_by_id(client, id)
.await
.expect(&format!("could not find element with id `{id}`"));
let value = el.prop("value").await?;
assert_eq!(value.as_deref(), Some(expected));
Ok(())
}

View File

@@ -25,21 +25,3 @@ async fn i_see_the_navbar(world: &mut AppWorld) -> Result<()> {
check::element_exists(client, "nav").await?;
Ok(())
}
#[then(regex = r"^I see ([\d\w]+) is selected$")]
async fn i_see_the_select(world: &mut AppWorld, id: String) -> Result<()> {
let client = &world.client;
check::select_option_is_selected(client, &id).await?;
Ok(())
}
#[then(regex = r"^I see the value of (\w+) is (.*)$")]
async fn i_see_the_value(
world: &mut AppWorld,
id: String,
value: String,
) -> Result<()> {
let client = &world.client;
check::element_value_is(client, &id, &value).await?;
Ok(())
}

View File

@@ -1,7 +1,4 @@
use crate::{
issue_4005::Routes4005, issue_4088::Routes4088, issue_4217::Routes4217,
pr_4015::Routes4015, pr_4091::Routes4091,
};
use crate::{issue_4088::Routes4088, pr_4015::Routes4015, pr_4091::Routes4091};
use leptos::prelude::*;
use leptos_meta::{MetaTags, *};
use leptos_router::{
@@ -40,8 +37,6 @@ pub fn App() -> impl IntoView {
<Routes4091/>
<Routes4015/>
<Routes4088/>
<Routes4217/>
<Routes4005/>
</Routes>
</main>
</Router>
@@ -64,8 +59,6 @@ fn HomePage() -> impl IntoView {
<li><a href="/4091/">"4091"</a></li>
<li><a href="/4015/">"4015"</a></li>
<li><a href="/4088/">"4088"</a></li>
<li><a href="/4217/">"4217"</a></li>
<li><a href="/4005/">"4005"</a></li>
</ul>
</nav>
}

View File

@@ -1,24 +0,0 @@
use leptos::prelude::*;
#[allow(unused_imports)]
use leptos_router::{
components::Route, path, MatchNestedRoutes, NavigateOptions,
};
#[component]
pub fn Routes4005() -> impl MatchNestedRoutes + Clone {
view! {
<Route path=path!("4005") view=Issue4005/>
}
.into_inner()
}
#[component]
fn Issue4005() -> impl IntoView {
view! {
<select id="select" prop:value="2">
<option value="1">"Option 1"</option>
<option value="2">"Option 2"</option>
<option value="3">"Option 3"</option>
</select>
}
}

View File

@@ -1,24 +0,0 @@
use leptos::prelude::*;
#[allow(unused_imports)]
use leptos_router::{
components::Route, path, MatchNestedRoutes, NavigateOptions,
};
#[component]
pub fn Routes4217() -> impl MatchNestedRoutes + Clone {
view! {
<Route path=path!("4217") view=Issue4217/>
}
.into_inner()
}
#[component]
fn Issue4217() -> impl IntoView {
view! {
<select multiple=true>
<option id="option1" value="1" selected>"Option 1"</option>
<option id="option2" value="2" selected>"Option 2"</option>
<option id="option3" value="3" selected>"Option 3"</option>
</select>
}
}

View File

@@ -1,7 +1,5 @@
pub mod app;
mod issue_4005;
mod issue_4088;
mod issue_4217;
mod pr_4015;
mod pr_4091;

View File

@@ -4,7 +4,7 @@ authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"
description = "Axum integrations for the Leptos web framework."
version = "0.8.6"
version = "0.8.5"
rust-version.workspace = true
edition.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos"
version = "0.8.8"
version = "0.8.6"
authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"

View File

@@ -27,6 +27,7 @@ pub fn AutoReload(
None => options.reload_port,
};
let protocol = match options.reload_ws_protocol {
leptos_config::ReloadWSProtocol::Auto => "null",
leptos_config::ReloadWSProtocol::WS => "'ws://'",
leptos_config::ReloadWSProtocol::WSS => "'wss://'",
};

View File

@@ -1,4 +1,9 @@
let host = window.location.hostname;
if (protocol === null) {
protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
}
let ws = new WebSocket(`${protocol}${host}:${reload_port}/live_reload`);
ws.onmessage = (ev) => {
let msg = JSON.parse(ev.data);

View File

@@ -5,7 +5,7 @@ license = "MIT"
repository = "https://github.com/leptos-rs/leptos"
description = "Configuration for the Leptos web framework."
readme = "../README.md"
version = "0.8.7"
version = "0.8.5"
rust-version.workspace = true
edition.workspace = true

View File

@@ -153,7 +153,7 @@ impl LeptosOptions {
None => None,
},
reload_ws_protocol: ws_from_str(
env_w_default("LEPTOS_RELOAD_WS_PROTOCOL", "ws")?.as_str(),
env_w_default("LEPTOS_RELOAD_WS_PROTOCOL", "auto")?.as_str(),
)?,
not_found_path: env_w_default("LEPTOS_NOT_FOUND_PATH", "/404")?
.into(),
@@ -283,22 +283,24 @@ impl TryFrom<String> for Env {
pub enum ReloadWSProtocol {
WS,
WSS,
Auto,
}
impl Default for ReloadWSProtocol {
fn default() -> Self {
Self::WS
Self::Auto
}
}
fn ws_from_str(input: &str) -> Result<ReloadWSProtocol, LeptosConfigError> {
let sanitized = input.to_lowercase();
match sanitized.as_ref() {
"auto" => Ok(ReloadWSProtocol::Auto),
"ws" | "WS" => Ok(ReloadWSProtocol::WS),
"wss" | "WSS" => Ok(ReloadWSProtocol::WSS),
_ => Err(LeptosConfigError::EnvVarError(format!(
"{input} is not a supported websocket protocol. Use only `ws` or \
`wss`.",
"{input} is not a supported websocket protocol. Use only `auto`, \
`ws` or `wss`.",
))),
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_dom"
version = "0.8.6"
version = "0.8.5"
authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"

View File

@@ -258,7 +258,15 @@ pub fn request_idle_callback_with_handle(
///
/// <div class="warning">The task is called outside of the ownership tree, this means that if you want to access for example the context you need to reestablish the owner.</div>
pub fn queue_microtask(task: impl FnOnce() + 'static) {
tachys::renderer::dom::queue_microtask(task);
use js_sys::{Function, Reflect};
let task = Closure::once_into_js(task);
let window = web_sys::window().expect("window not available");
let queue_microtask =
Reflect::get(&window, &JsValue::from_str("queueMicrotask"))
.expect("queueMicrotask not available");
let queue_microtask = queue_microtask.unchecked_into::<Function>();
_ = queue_microtask.call1(&JsValue::UNDEFINED, &task);
}
/// Handle that is generated by [set_timeout_with_handle] and can be used to clear the timeout.

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_macro"
version = "0.8.7"
version = "0.8.6"
authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"

View File

@@ -44,8 +44,7 @@ denylist = ["tracing"]
max_combination_size = 2
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition", "--cfg", "docsrs"]
all-features = true
rustdoc-args = ["--generate-link-to-definition"]
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }

View File

@@ -386,7 +386,6 @@ T: Send + Sync + 'static,
}
#[cfg(feature = "serde-wasm-bindgen")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde-wasm-bindgen")))]
impl<T> ArcOnceResource<T, JsonSerdeWasmCodec>
where
T: Send + Sync + 'static,
@@ -419,7 +418,6 @@ fut: impl Future<Output = T> + Send + 'static
}
}
#[cfg(feature = "miniserde")]
#[cfg_attr(docsrs, doc(cfg(feature = "miniserde")))]
impl<T> ArcOnceResource<T, MiniserdeCodec>
where
T: Send + Sync + 'static,
@@ -453,7 +451,6 @@ where
}
#[cfg(feature = "serde-lite")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde-lite")))]
impl<T> ArcOnceResource<T, SerdeLite<JsonSerdeCodec>>
where
T: Send + Sync + 'static,
@@ -487,7 +484,6 @@ fut: impl Future<Output = T> + Send + 'static
}
#[cfg(feature = "rkyv")]
#[cfg_attr(docsrs, doc(cfg(feature = "rkyv")))]
impl<T> ArcOnceResource<T, RkyvCodec>
where
T: Send + Sync + 'static,
@@ -752,7 +748,6 @@ T: Send + Sync + 'static,
}
#[cfg(feature = "serde-wasm-bindgen")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde-wasm-bindgen")))]
impl<T> OnceResource<T, JsonSerdeWasmCodec>
where
T: Send + Sync + 'static,
@@ -785,7 +780,6 @@ fut: impl Future<Output = T> + Send + 'static
}
}
#[cfg(feature = "miniserde")]
#[cfg_attr(docsrs, doc(cfg(feature = "miniserde")))]
impl<T> OnceResource<T, MiniserdeCodec>
where
T: Send + Sync + 'static,
@@ -819,7 +813,6 @@ where
}
#[cfg(feature = "serde-lite")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde-lite")))]
impl<T> OnceResource<T, SerdeLite<JsonSerdeCodec>>
where
T: Send + Sync + 'static,
@@ -853,7 +846,6 @@ fut: impl Future<Output = T> + Send + 'static
}
#[cfg(feature = "rkyv")]
#[cfg_attr(docsrs, doc(cfg(feature = "rkyv")))]
impl<T> OnceResource<T, RkyvCodec>
where
T: Send + Sync + 'static,

View File

@@ -709,7 +709,6 @@ where
}
#[cfg(feature = "rkyv")]
#[cfg_attr(docsrs, doc(cfg(feature = "rkyv")))]
impl<T> ArcResource<T, RkyvCodec>
where
RkyvCodec: Encoder<T> + Decoder<T>,
@@ -1049,7 +1048,6 @@ where
}
#[cfg(feature = "serde-wasm-bindgen")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde-wasm-bindgen")))]
impl<T> Resource<T, JsonSerdeWasmCodec>
where
JsonSerdeWasmCodec: Encoder<T> + Decoder<T>,
@@ -1107,7 +1105,6 @@ where
}
#[cfg(feature = "miniserde")]
#[cfg_attr(docsrs, doc(cfg(feature = "miniserde")))]
impl<T> Resource<T, MiniserdeCodec>
where
MiniserdeCodec: Encoder<T> + Decoder<T>,
@@ -1167,7 +1164,6 @@ where
}
#[cfg(feature = "serde-lite")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde-lite")))]
impl<T> Resource<T, SerdeLite<JsonSerdeCodec>>
where
SerdeLite<JsonSerdeCodec>: Encoder<T> + Decoder<T>,
@@ -1226,7 +1222,6 @@ where
}
#[cfg(feature = "rkyv")]
#[cfg_attr(docsrs, doc(cfg(feature = "rkyv")))]
impl<T> Resource<T, RkyvCodec>
where
RkyvCodec: Encoder<T> + Decoder<T>,

View File

@@ -80,7 +80,6 @@ where
}
#[cfg(feature = "serde-lite")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde-lite")))]
impl<T> SharedValue<T, SerdeLite<JsonSerdeCodec>>
where
SerdeLite<JsonSerdeCodec>: Encoder<T> + Decoder<T>,
@@ -103,7 +102,6 @@ where
}
#[cfg(feature = "serde-wasm-bindgen")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde-wasm-bindgen")))]
impl<T> SharedValue<T, JsonSerdeWasmCodec>
where
JsonSerdeWasmCodec: Encoder<T> + Decoder<T>,
@@ -126,7 +124,6 @@ where
}
#[cfg(feature = "miniserde")]
#[cfg_attr(docsrs, doc(cfg(feature = "miniserde")))]
impl<T> SharedValue<T, MiniserdeCodec>
where
MiniserdeCodec: Encoder<T> + Decoder<T>,
@@ -149,7 +146,6 @@ where
}
#[cfg(feature = "rkyv")]
#[cfg_attr(docsrs, doc(cfg(feature = "rkyv")))]
impl<T> SharedValue<T, RkyvCodec>
where
RkyvCodec: Encoder<T> + Decoder<T>,

View File

@@ -1,6 +1,6 @@
[package]
name = "reactive_graph"
version = "0.2.6"
version = "0.2.5"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -1,6 +1,6 @@
[package]
name = "reactive_stores_macro"
version = "0.2.6"
version = "0.2.5"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_router"
version = "0.8.6"
version = "0.8.5"
authors = ["Greg Johnston", "Ben Wishovich"]
license = "MIT"
readme = "../README.md"

View File

@@ -5,7 +5,7 @@ license = "MIT"
repository = "https://github.com/leptos-rs/leptos"
description = "RPC for any web framework."
readme = "../README.md"
version = "0.8.6"
version = "0.8.5"
rust-version.workspace = true
edition.workspace = true

View File

@@ -568,7 +568,7 @@ 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;
/// Converts the custom error type to a [`String`].
/// 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| {
Self::Encoder::encode(&Self::from_server_fn_error(
@@ -581,7 +581,7 @@ pub trait FromServerFnError: std::fmt::Debug + Sized + 'static {
})
}
/// Deserializes the custom error type from a [`&str`].
/// Deserializes the custom error type, according to the encoding given by `Self::Encoding`.
fn de(data: Bytes) -> Self {
Self::Encoder::decode(data).unwrap_or_else(|e| {
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()

View File

@@ -319,6 +319,12 @@ pub trait ServerFn: Send + Sized {
)
});
if err.is_some() {
res.set_content_type(
<<Self::Error as FromServerFnError>::Encoder>::CONTENT_TYPE,
);
}
// if it accepts HTML, we'll redirect to the Referer
#[cfg(feature = "form-redirects")]
if accepts_html {

View File

@@ -72,6 +72,8 @@ mod axum {
let inner = self.call(req);
Box::pin(async move {
inner.await.unwrap_or_else(|e| {
// TODO: this needs to set the Content-Type correctly depending on the error type
// note that this only applies to middleware errors
let err =
ser(ServerFnErrorErr::MiddlewareError(e.to_string()));
Response::<Body>::error_response(&path, err)
@@ -149,6 +151,8 @@ mod actix {
let inner = self.call(req);
Box::pin(async move {
inner.await.unwrap_or_else(|e| {
// TODO: this needs to set the Content-Type correctly depending on the error type
// note that this only applies to middleware errors
let err =
ser(ServerFnErrorErr::MiddlewareError(e.to_string()));
ActixResponse::error_response(&path, err).take()

View File

@@ -4,8 +4,7 @@ use crate::error::{
};
use actix_web::{
http::{
header,
header::{HeaderValue, LOCATION},
header::{self, HeaderValue, CONTENT_TYPE, LOCATION},
StatusCode,
},
HttpResponse,
@@ -80,6 +79,12 @@ impl Res for ActixResponse {
))
}
fn set_content_type(&mut self, content_type: &str) {
self.0
.headers_mut()
.append(CONTENT_TYPE, HeaderValue::from_str(content_type).unwrap());
}
fn redirect(&mut self, path: &str) {
if let Ok(path) = HeaderValue::from_str(path) {
*self.0.status_mut() = StatusCode::FOUND;

View File

@@ -19,7 +19,10 @@ use crate::error::{
};
use bytes::Bytes;
use futures::{Stream, TryStreamExt};
use http::{header, HeaderValue, Response, StatusCode};
use http::{
header::{self, CONTENT_TYPE},
HeaderValue, Response, StatusCode,
};
use std::pin::Pin;
use throw_error::Error;
@@ -100,6 +103,11 @@ impl Res for Response<Body> {
.unwrap()
}
fn set_content_type(&mut self, content_type: &str) {
self.headers_mut()
.append(CONTENT_TYPE, HeaderValue::from_str(content_type).unwrap());
}
fn redirect(&mut self, path: &str) {
if let Ok(path) = HeaderValue::from_str(path) {
self.headers_mut().insert(header::LOCATION, path);

View File

@@ -6,7 +6,10 @@ use crate::error::{
use axum::body::Body;
use bytes::Bytes;
use futures::{Stream, TryStreamExt};
use http::{header, HeaderValue, Response, StatusCode};
use http::{
header::{self, CONTENT_TYPE},
HeaderValue, Response, StatusCode,
};
impl<E> TryRes<E> for Response<Body>
where
@@ -60,6 +63,11 @@ impl Res for Response<Body> {
.unwrap()
}
fn set_content_type(&mut self, content_type: &str) {
self.headers_mut()
.append(CONTENT_TYPE, HeaderValue::from_str(content_type).unwrap());
}
fn redirect(&mut self, path: &str) {
if let Ok(path) = HeaderValue::from_str(path) {
self.headers_mut().insert(header::LOCATION, path);

View File

@@ -37,9 +37,16 @@ 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 text as its body.
/// 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;
/// Sets the `Content-Type` header on the response.
fn set_content_type(&mut self, content_type: &str) {
// TODO 0.9: remove this method and default implementation
// it is included here to make this change non-breaking
_ = content_type;
}
/// Redirect the response by setting a 302 code and Location header.
fn redirect(&mut self, path: &str);
}
@@ -103,6 +110,11 @@ impl Res for BrowserMockRes {
unreachable!()
}
fn set_content_type(&mut self, content_type: &str) {
_ = content_type;
unreachable!()
}
fn redirect(&mut self, _path: &str) {
unreachable!()
}

View File

@@ -5,7 +5,7 @@ license = "MIT"
repository = "https://github.com/leptos-rs/leptos"
description = "RPC for any web framework."
readme = "../README.md"
version = "0.8.7"
version = "0.8.6"
edition.workspace = true
[dependencies]

View File

@@ -1,6 +1,6 @@
[package]
name = "tachys"
version = "0.2.7"
version = "0.2.6"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -329,8 +329,6 @@ where
fn build(self) -> Self::State {
let el = Rndr::create_element(self.tag.tag(), E::NAMESPACE);
let attrs = self.attributes.build(&el);
let children = if E::SELF_CLOSING {
None
} else {
@@ -339,6 +337,8 @@ where
Some(children)
};
let attrs = self.attributes.build(&el);
ElementState {
el,
attrs,

View File

@@ -202,7 +202,7 @@ macro_rules! prop_type {
key: &str,
) -> Self::State {
let value = self.into();
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
(el.clone(), value)
}
@@ -212,14 +212,14 @@ macro_rules! prop_type {
key: &str,
) -> Self::State {
let value = self.into();
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
(el.clone(), value)
}
fn rebuild(self, state: &mut Self::State, key: &str) {
let (el, prev) = state;
let value = self.into();
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
*prev = value;
}
@@ -245,7 +245,7 @@ macro_rules! prop_type {
let was_some = self.is_some();
let value = self.into();
if was_some {
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
}
(el.clone(), value)
}
@@ -258,7 +258,7 @@ macro_rules! prop_type {
let was_some = self.is_some();
let value = self.into();
if was_some {
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
}
(el.clone(), value)
}
@@ -266,7 +266,7 @@ macro_rules! prop_type {
fn rebuild(self, state: &mut Self::State, key: &str) {
let (el, prev) = state;
let value = self.into();
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
*prev = value;
}
@@ -294,7 +294,7 @@ macro_rules! prop_type_str {
key: &str,
) -> Self::State {
let value = JsValue::from(&*self);
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
(el.clone(), value)
}
@@ -304,14 +304,14 @@ macro_rules! prop_type_str {
key: &str,
) -> Self::State {
let value = JsValue::from(&*self);
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
(el.clone(), value)
}
fn rebuild(self, state: &mut Self::State, key: &str) {
let (el, prev) = state;
let value = JsValue::from(&*self);
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
*prev = value;
}
@@ -339,7 +339,7 @@ macro_rules! prop_type_str {
let was_some = self.is_some();
let value = JsValue::from(self.map(|n| JsValue::from_str(&n)));
if was_some {
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
}
(el.clone(), value)
}
@@ -352,7 +352,7 @@ macro_rules! prop_type_str {
let was_some = self.is_some();
let value = JsValue::from(self.map(|n| JsValue::from_str(&n)));
if was_some {
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
}
(el.clone(), value)
}
@@ -360,7 +360,7 @@ macro_rules! prop_type_str {
fn rebuild(self, state: &mut Self::State, key: &str) {
let (el, prev) = state;
let value = JsValue::from(self.map(|n| JsValue::from_str(&n)));
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
*prev = value;
}
@@ -392,7 +392,7 @@ impl IntoProperty for Arc<str> {
key: &str,
) -> Self::State {
let value = JsValue::from_str(self.as_ref());
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
(el.clone(), value)
}
@@ -402,14 +402,14 @@ impl IntoProperty for Arc<str> {
key: &str,
) -> Self::State {
let value = JsValue::from_str(self.as_ref());
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
(el.clone(), value)
}
fn rebuild(self, state: &mut Self::State, key: &str) {
let (el, prev) = state;
let value = JsValue::from_str(self.as_ref());
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
*prev = value;
}
@@ -435,7 +435,7 @@ impl IntoProperty for Option<Arc<str>> {
let was_some = self.is_some();
let value = JsValue::from(self.map(|n| JsValue::from_str(&n)));
if was_some {
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
}
(el.clone(), value)
}
@@ -448,7 +448,7 @@ impl IntoProperty for Option<Arc<str>> {
let was_some = self.is_some();
let value = JsValue::from(self.map(|n| JsValue::from_str(&n)));
if was_some {
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
}
(el.clone(), value)
}
@@ -456,7 +456,7 @@ impl IntoProperty for Option<Arc<str>> {
fn rebuild(self, state: &mut Self::State, key: &str) {
let (el, prev) = state;
let value = JsValue::from(self.map(|n| JsValue::from_str(&n)));
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
*prev = value;
}

View File

@@ -261,7 +261,7 @@ impl IntoProperty for Oco<'static, str> {
key: &str,
) -> Self::State {
let value = JsValue::from_str(self.as_ref());
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
(el.clone(), value)
}
@@ -271,14 +271,14 @@ impl IntoProperty for Oco<'static, str> {
key: &str,
) -> Self::State {
let value = JsValue::from_str(self.as_ref());
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
(el.clone(), value)
}
fn rebuild(self, state: &mut Self::State, key: &str) {
let (el, prev) = state;
let value = JsValue::from_str(self.as_ref());
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
*prev = value;
}

View File

@@ -36,46 +36,6 @@ pub type ClassList = web_sys::DomTokenList;
pub type CssStyleDeclaration = web_sys::CssStyleDeclaration;
pub type TemplateElement = web_sys::HtmlTemplateElement;
/// A microtask is a short function which will run after the current task has
/// completed its work and when there is no other code waiting to be run before
/// control of the execution context is returned to the browser's event loop.
///
/// Microtasks are especially useful for libraries and frameworks that need
/// to perform final cleanup or other just-before-rendering tasks.
///
/// [MDN queueMicrotask](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask)
pub fn queue_microtask(task: impl FnOnce() + 'static) {
use js_sys::{Function, Reflect};
let task = Closure::once_into_js(task);
let window = window();
let queue_microtask =
Reflect::get(&window, &JsValue::from_str("queueMicrotask"))
.expect("queueMicrotask not available");
let queue_microtask = queue_microtask.unchecked_into::<Function>();
_ = queue_microtask.call1(&JsValue::UNDEFINED, &task);
}
fn queue(fun: Box<dyn FnOnce()>) {
use std::cell::{Cell, RefCell};
thread_local! {
static PENDING: Cell<bool> = const { Cell::new(false) };
static QUEUE: RefCell<Vec<Box<dyn FnOnce()>>> = RefCell::new(Vec::new());
}
QUEUE.with_borrow_mut(|q| q.push(fun));
if !PENDING.replace(true) {
queue_microtask(|| {
let tasks = QUEUE.take();
for task in tasks {
task();
}
PENDING.set(false);
})
}
}
impl Dom {
pub fn intern(text: &str) -> &str {
intern(text)
@@ -251,20 +211,6 @@ impl Dom {
}
}
pub fn set_property_or_value(el: &Element, key: &str, value: &JsValue) {
if key == "value" {
queue(Box::new({
let el = el.clone();
let value = value.clone();
move || {
Self::set_property(&el, "value", &value);
}
}))
} else {
Self::set_property(el, key, value);
}
}
pub fn set_property(el: &Element, key: &str, value: &JsValue) {
or_debug!(
js_sys::Reflect::set(