diff --git a/integrations/actix/src/lib.rs b/integrations/actix/src/lib.rs
index a29e3434b2..9d7b422183 100644
--- a/integrations/actix/src/lib.rs
+++ b/integrations/actix/src/lib.rs
@@ -25,7 +25,8 @@ use leptos::{
*,
};
use leptos_integration_utils::{
- build_async_response, html_parts_separated, referrer_to_url, WithServerFn,
+ build_async_response, filter_server_fn_url_errors, html_parts_separated,
+ referrer_to_url, WithServerFn,
};
use leptos_meta::*;
use leptos_router::*;
@@ -300,13 +301,19 @@ pub fn handle_server_fns_with_context(
let url = req
.headers()
.get(header::REFERER)
- .and_then(|referrer| referrer_to_url(referrer, fn_name.as_str()));
+ .and_then(|referrer| {
+ referrer_to_url(referrer, fn_name.as_str())
+ });
if let Some(url) = url {
HttpResponse::SeeOther()
.insert_header((
header::LOCATION,
- url.with_server_fn(&e, fn_name.as_str()).as_str(),
+ url.with_server_fn(
+ &e,
+ fn_name.as_str(),
+ )
+ .as_str(),
))
.finish()
} else {
@@ -754,6 +761,15 @@ fn provide_contexts(req: &HttpRequest, res_options: ResponseOptions) {
provide_context(MetaContext::new());
provide_context(res_options);
provide_context(req.clone());
+ if let Some(referrer) = req.headers().get(header::REFERER) {
+ leptos::logging::log!("Referrer = {referrer:?}");
+ provide_context(filter_server_fn_url_errors(
+ referrer
+ .to_str()
+ .expect("Invalid referer header from browser request"),
+ ));
+ }
+
provide_server_redirect(redirect);
#[cfg(feature = "nonce")]
leptos::nonce::provide_nonce();
diff --git a/integrations/utils/src/lib.rs b/integrations/utils/src/lib.rs
index f8f38adfee..044df763e8 100644
--- a/integrations/utils/src/lib.rs
+++ b/integrations/utils/src/lib.rs
@@ -1,10 +1,13 @@
use futures::{Stream, StreamExt};
use http::HeaderValue;
-use leptos::{nonce::use_nonce, use_context, RuntimeId, ServerFnError};
+use leptos::{
+ nonce::use_nonce, server_fn::error::ServerFnUrlError, use_context,
+ RuntimeId, ServerFnError,
+};
use leptos_config::LeptosOptions;
use leptos_meta::MetaContext;
use regex::Regex;
-use serde::{Serialize, Deserialize};
+use std::collections::HashSet;
use url::Url;
extern crate tracing;
@@ -162,10 +165,24 @@ pub async fn build_async_response(
format!("{head}
{buf}{tail}")
}
-#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
-pub struct ServerFnUrlError {
- pub error: ServerFnError,
- pub fn_name: String
+pub fn filter_server_fn_url_errors<'a>(
+ referrer: impl Into<&'a str>,
+) -> HashSet {
+ Url::parse(referrer.into())
+ .expect("Cannot parse referrer from page request")
+ .query_pairs()
+ .into_iter()
+ .filter_map(|(k, v)| {
+
+ let foo = if k.starts_with("server_fn_error_") {
+ serde_qs::from_str::<'_, ServerFnUrlError>(v.as_ref()).ok()
+ } else {
+ None
+ };
+ leptos::logging::log!("Parsed query key {k} with value {foo:?}");
+ foo
+ })
+ .collect()
}
pub fn referrer_to_url(referer: &HeaderValue, fn_name: &str) -> Option {
@@ -185,9 +202,12 @@ impl WithServerFn for Url {
fn with_server_fn(mut self, error: &ServerFnError, fn_name: &str) -> Self {
self.query_pairs_mut().append_pair(
format!("server_fn_error_{fn_name}").as_str(),
- serde_qs::to_string(&ServerFnUrlError{error: error.to_owned(), fn_name: fn_name.to_owned()})
- .expect("Could not serialize server fn error!")
- .as_str(),
+ serde_qs::to_string(&ServerFnUrlError::new(
+ fn_name.to_owned(),
+ error.to_owned(),
+ ))
+ .expect("Could not serialize server fn error!")
+ .as_str(),
);
self
diff --git a/router/src/components/form.rs b/router/src/components/form.rs
index 2177c442eb..813e8786ca 100644
--- a/router/src/components/form.rs
+++ b/router/src/components/form.rs
@@ -2,9 +2,9 @@ use crate::{
hooks::has_router, use_navigate, use_resolved_path, NavigateOptions,
ToHref, Url,
};
-use leptos::{html::form, logging::*, *};
+use leptos::{html::form, logging::*, server_fn::error::ServerFnUrlError, *};
use serde::{de::DeserializeOwned, Serialize};
-use std::{error::Error, rc::Rc};
+use std::{collections::HashSet, error::Error, rc::Rc};
use wasm_bindgen::{JsCast, UnwrapThrowExt};
use wasm_bindgen_futures::JsFuture;
use web_sys::RequestRedirect;
@@ -450,6 +450,20 @@ where
let version = action.version();
let value = action.value();
let input = action.input();
+ let errors = use_context::>();
+
+ if let (Some(url_error), Some(error)) = (
+ errors
+ .map(|errors| {
+ errors
+ .into_iter()
+ .find(|e| e.fn_name().contains(&action_url))
+ })
+ .flatten(),
+ error,
+ ) {
+ error.try_set(Some(Box::new(ServerFnErrorErr::from(url_error))));
+ }
let on_error = Rc::new(move |e: &gloo_net::Error| {
batch(move || {
diff --git a/server_fn/src/error.rs b/server_fn/src/error.rs
index d345df6b7d..24c5ba0e61 100644
--- a/server_fn/src/error.rs
+++ b/server_fn/src/error.rs
@@ -54,7 +54,7 @@ impl From for Error {
/// Unlike [`ServerFnErrorErr`], this does not implement [`std::error::Error`].
/// This means that other error types can easily be converted into it using the
/// `?` operator.
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(tag = "type", content = "message")]
pub enum ServerFnError {
/// Error while trying to register the server function (only occurs in case of poisoned RwLock).
@@ -73,6 +73,45 @@ pub enum ServerFnError {
MissingArg(String),
}
+/// TODO: Write Documentation
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
+pub struct ServerFnUrlError {
+ internal_error: ServerFnError,
+ internal_fn_name: String,
+}
+
+impl ServerFnUrlError {
+ /// TODO: Write Documentation
+ pub fn new(fn_name: String, error: ServerFnError) -> Self {
+ Self {
+ internal_fn_name: fn_name,
+ internal_error: error,
+ }
+ }
+
+ /// TODO: Write documentation
+ pub fn error(&self) -> &ServerFnError {
+ &self.internal_error
+ }
+
+ /// TODO: Add docs
+ pub fn fn_name(&self) -> &str {
+ &self.internal_fn_name.as_ref()
+ }
+}
+
+impl From for ServerFnError {
+ fn from(error: ServerFnUrlError) -> Self {
+ error.internal_error
+ }
+}
+
+impl From for ServerFnErrorErr {
+ fn from(error: ServerFnUrlError) -> Self {
+ error.internal_error.into()
+ }
+}
+
impl core::fmt::Display for ServerFnError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(