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!(