Compare commits

...

1 Commits

Author SHA1 Message Date
Greg Johnston
55ea00afdd Router sets status code 404 when it can't match a route and returns fallback 2023-01-16 20:50:48 -05:00
4 changed files with 64 additions and 11 deletions

View File

@@ -259,20 +259,22 @@ where
let options = options.clone();
let app_fn = app_fn.clone();
let res_options = ResponseOptions::default();
let status = RouterStatusContext::default();
async move {
let app = {
let app_fn = app_fn.clone();
let res_options = res_options.clone();
let status = status.clone();
move |cx| {
provide_contexts(cx, &req, res_options);
provide_contexts(cx, &req, res_options, status);
(app_fn)(cx).into_view(cx)
}
};
let (head, tail) = html_parts(&options);
stream_app(app, head, tail, res_options).await
stream_app(app, head, tail, res_options, status).await
}
})
}
@@ -336,6 +338,7 @@ where
let app_fn = app_fn.clone();
let data_fn = data_fn.clone();
let res_options = ResponseOptions::default();
let status = RouterStatusContext::default();
async move {
let data = match data_fn(req.clone()).await {
@@ -347,24 +350,31 @@ where
let app = {
let app_fn = app_fn.clone();
let res_options = res_options.clone();
let status = status.clone();
move |cx| {
provide_contexts(cx, &req, res_options);
provide_contexts(cx, &req, res_options, status);
(app_fn)(cx, data).into_view(cx)
}
};
let (head, tail) = html_parts(&options);
stream_app(app, head, tail, res_options).await
stream_app(app, head, tail, res_options, status).await
}
})
}
fn provide_contexts(cx: leptos::Scope, req: &HttpRequest, res_options: ResponseOptions) {
fn provide_contexts(
cx: leptos::Scope,
req: &HttpRequest,
res_options: ResponseOptions,
status: RouterStatusContext,
) {
let path = leptos_corrected_path(req);
let integration = ServerIntegration { path };
provide_context(cx, RouterIntegrationContext::new(integration));
provide_context(cx, status);
provide_context(cx, MetaContext::new());
provide_context(cx, res_options);
provide_context(cx, req.clone());
@@ -385,6 +395,7 @@ async fn stream_app(
head: String,
tail: String,
res_options: ResponseOptions,
router_status: RouterStatusContext,
) -> HttpResponse<BoxBody> {
let (stream, runtime, _) = render_to_stream_with_prefix_undisposed(app, move |cx| {
let head = use_context::<MetaContext>(cx)
@@ -411,7 +422,15 @@ async fn stream_app(
let res_options = res_options.0.read().await;
let (status, mut headers) = (res_options.status, res_options.headers.clone());
let status = status.unwrap_or_default();
let status = status.unwrap_or_else(|| {
router_status
.status
.read()
.ok()
.and_then(|s| s.map(|s| StatusCode::from_u16(s).ok()))
.flatten()
.unwrap_or_default()
});
let complete_stream = futures::stream::iter([
first_chunk.unwrap(),

View File

@@ -318,6 +318,8 @@ where
let default_res_options = ResponseOptions::default();
let res_options2 = default_res_options.clone();
let res_options3 = default_res_options.clone();
let router_status = RouterStatusContext::default();
let router_status2 = router_status.clone();
async move {
// Need to get the path and query string of the Request
@@ -403,6 +405,7 @@ where
cx,
RouterIntegrationContext::new(integration),
);
provide_context(cx, router_status2);
provide_context(cx, MetaContext::new());
provide_context(cx, req_parts);
provide_context(cx, default_res_options);
@@ -471,9 +474,16 @@ where
Box::pin(complete_stream) as PinnedHtmlStream
));
if let Some(status) = res_options.status {
*res.status_mut() = status
}
let status = res_options.status.unwrap_or_else(|| {
router_status
.status
.read()
.ok()
.and_then(|s| s.map(|s| StatusCode::from_u16(s).ok()))
.flatten()
.unwrap_or_default()
});
*res.status_mut() = status;
let mut res_headers = res_options.headers.clone();
res.headers_mut().extend(res_headers.drain());

View File

@@ -1,5 +1,9 @@
use cfg_if::cfg_if;
use std::{cell::RefCell, rc::Rc};
use std::{
cell::RefCell,
rc::Rc,
sync::{Arc, RwLock},
};
use leptos::*;
use thiserror::Error;
@@ -61,6 +65,14 @@ pub(crate) struct RouterContextInner {
set_state: WriteSignal<State>,
}
/// Context type that indicates the status of the last request
/// (i.e., whether it was not found, or had an error.)
#[derive(Debug, Clone, Default)]
pub struct RouterStatusContext {
pub status: Arc<RwLock<Option<u16>>>,
pub message: Arc<RwLock<Option<String>>>,
}
impl std::fmt::Debug for RouterContextInner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RouterContextInner")

View File

@@ -12,7 +12,7 @@ use crate::{
expand_optionals, get_route_matches, join_paths, Branch, Matcher, RouteDefinition,
RouteMatch,
},
RouteContext, RouterContext,
RouteContext, RouterContext, RouterStatusContext,
};
/// Contains route definitions and manages the actual routing process.
@@ -190,12 +190,24 @@ pub fn Routes(
});
// show the root route
let router_status = use_context::<RouterStatusContext>(cx);
let root = create_memo(cx, move |prev| {
provide_context(cx, route_states);
let router_status = router_status.clone();
route_states.with(|state| {
if state.routes.borrow().is_empty() {
if let Some(status) = router_status {
if let Ok(mut lock) = status.status.write() {
*lock = Some(404);
}
}
Some(base_route.outlet().into_view(cx))
} else {
if let Some(status) = router_status {
if let Ok(mut lock) = status.status.write() {
*lock = None;
}
}
let root = state.routes.borrow();
let root = root.get(0);
if let Some(route) = root {