Skip to content

Commit

Permalink
Make error gettable by server rendered action form
Browse files Browse the repository at this point in the history
  • Loading branch information
SleeplessOne1917 committed Jan 9, 2024
1 parent ed389bf commit a3c8fe8
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 15 deletions.
22 changes: 19 additions & 3 deletions integrations/actix/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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();
Expand Down
38 changes: 29 additions & 9 deletions integrations/utils/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -162,10 +165,24 @@ pub async fn build_async_response(
format!("{head}<body{body_meta}>{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<ServerFnUrlError> {
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<Url> {
Expand All @@ -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
Expand Down
18 changes: 16 additions & 2 deletions router/src/components/form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -450,6 +450,20 @@ where
let version = action.version();
let value = action.value();
let input = action.input();
let errors = use_context::<HashSet<ServerFnUrlError>>();

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 || {
Expand Down
41 changes: 40 additions & 1 deletion server_fn/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ impl From<ServerFnError> 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).
Expand All @@ -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<ServerFnUrlError> for ServerFnError {
fn from(error: ServerFnUrlError) -> Self {
error.internal_error
}
}

impl From<ServerFnUrlError> 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!(
Expand Down

0 comments on commit a3c8fe8

Please sign in to comment.