mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
feat: allow multiple HTTP request methods/verbs (#695)
This commit is contained in:
parent
f969fd7eff
commit
764192af36
7 changed files with 412 additions and 130 deletions
|
@ -18,6 +18,7 @@ cfg_if! {
|
|||
_ = GetTodos::register();
|
||||
_ = AddTodo::register();
|
||||
_ = DeleteTodo::register();
|
||||
_ = FormDataHandler::register();
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
|
||||
|
@ -106,6 +107,24 @@ pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> {
|
|||
.map_err(|e| ServerFnError::ServerError(e.to_string()))
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct FormData {
|
||||
hi: String
|
||||
}
|
||||
|
||||
#[server(FormDataHandler, "/api")]
|
||||
pub async fn form_data(cx: Scope) -> Result<FormData, ServerFnError> {
|
||||
use axum::extract::FromRequest;
|
||||
|
||||
let req = use_context::<leptos_axum::LeptosRequest<axum::body::Body>>(cx).and_then(|req| req.take_request()).unwrap();
|
||||
if req.method() == http::Method::POST {
|
||||
let form = axum::Form::from_request(req, &()).await.map_err(|e| ServerFnError::ServerError(e.to_string()))?;
|
||||
Ok(form.0)
|
||||
} else {
|
||||
Err(ServerFnError::ServerError("wrong form fields submitted".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn TodoApp(cx: Scope) -> impl IntoView {
|
||||
//let id = use_context::<String>(cx);
|
||||
|
@ -126,6 +145,23 @@ pub fn TodoApp(cx: Scope) -> impl IntoView {
|
|||
<Todos/>
|
||||
</ErrorBoundary>
|
||||
}/> //Route
|
||||
<Route path="weird" methods=&[Method::Get, Method::Post]
|
||||
ssr=SsrMode::Async
|
||||
view=|cx| {
|
||||
let res = create_resource(cx, || (), move |_| async move {
|
||||
form_data(cx).await
|
||||
});
|
||||
view! { cx,
|
||||
<Suspense fallback=|| ()>
|
||||
<pre>
|
||||
{move || {
|
||||
res.with(cx, |body| format!("{body:#?}"))
|
||||
}}
|
||||
</pre>
|
||||
</Suspense>
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
|
@ -147,6 +183,10 @@ pub fn Todos(cx: Scope) -> impl IntoView {
|
|||
|
||||
view! {
|
||||
cx,
|
||||
<form method="POST" action="/weird">
|
||||
<input type="text" name="hi" value="John"/>
|
||||
<input type="submit"/>
|
||||
</form>
|
||||
<div>
|
||||
<MultiActionForm action=add_todo>
|
||||
<label>
|
||||
|
|
|
@ -298,6 +298,7 @@ pub fn handle_server_fns_with_context(
|
|||
/// ```
|
||||
/// use actix_web::{App, HttpServer};
|
||||
/// use leptos::*;
|
||||
/// use leptos_router::Method;
|
||||
/// use std::{env, net::SocketAddr};
|
||||
///
|
||||
/// #[component]
|
||||
|
@ -321,6 +322,7 @@ pub fn handle_server_fns_with_context(
|
|||
/// leptos_actix::render_app_to_stream(
|
||||
/// leptos_options.to_owned(),
|
||||
/// |cx| view! { cx, <MyApp/> },
|
||||
/// Method::Get,
|
||||
/// ),
|
||||
/// )
|
||||
/// })
|
||||
|
@ -340,11 +342,12 @@ pub fn handle_server_fns_with_context(
|
|||
pub fn render_app_to_stream<IV>(
|
||||
options: LeptosOptions,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + 'static,
|
||||
method: Method,
|
||||
) -> Route
|
||||
where
|
||||
IV: IntoView,
|
||||
{
|
||||
render_app_to_stream_with_context(options, |_cx| {}, app_fn)
|
||||
render_app_to_stream_with_context(options, |_cx| {}, app_fn, method)
|
||||
}
|
||||
|
||||
/// Returns an Actix [Route](actix_web::Route) that listens for a `GET` request and tries
|
||||
|
@ -363,6 +366,7 @@ where
|
|||
/// ```
|
||||
/// use actix_web::{App, HttpServer};
|
||||
/// use leptos::*;
|
||||
/// use leptos_router::Method;
|
||||
/// use std::{env, net::SocketAddr};
|
||||
///
|
||||
/// #[component]
|
||||
|
@ -386,6 +390,7 @@ where
|
|||
/// leptos_actix::render_app_to_stream_in_order(
|
||||
/// leptos_options.to_owned(),
|
||||
/// |cx| view! { cx, <MyApp/> },
|
||||
/// Method::Get,
|
||||
/// ),
|
||||
/// )
|
||||
/// })
|
||||
|
@ -405,11 +410,17 @@ where
|
|||
pub fn render_app_to_stream_in_order<IV>(
|
||||
options: LeptosOptions,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + 'static,
|
||||
method: Method,
|
||||
) -> Route
|
||||
where
|
||||
IV: IntoView,
|
||||
{
|
||||
render_app_to_stream_in_order_with_context(options, |_cx| {}, app_fn)
|
||||
render_app_to_stream_in_order_with_context(
|
||||
options,
|
||||
|_cx| {},
|
||||
app_fn,
|
||||
method,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns an Actix [Route](actix_web::Route) that listens for a `GET` request and tries
|
||||
|
@ -426,6 +437,7 @@ where
|
|||
/// ```
|
||||
/// use actix_web::{App, HttpServer};
|
||||
/// use leptos::*;
|
||||
/// use leptos_router::Method;
|
||||
/// use std::{env, net::SocketAddr};
|
||||
///
|
||||
/// #[component]
|
||||
|
@ -449,6 +461,7 @@ where
|
|||
/// leptos_actix::render_app_async(
|
||||
/// leptos_options.to_owned(),
|
||||
/// |cx| view! { cx, <MyApp/> },
|
||||
/// Method::Get,
|
||||
/// ),
|
||||
/// )
|
||||
/// })
|
||||
|
@ -468,11 +481,12 @@ where
|
|||
pub fn render_app_async<IV>(
|
||||
options: LeptosOptions,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + 'static,
|
||||
method: Method,
|
||||
) -> Route
|
||||
where
|
||||
IV: IntoView,
|
||||
{
|
||||
render_app_async_with_context(options, |_cx| {}, app_fn)
|
||||
render_app_async_with_context(options, |_cx| {}, app_fn, method)
|
||||
}
|
||||
|
||||
/// Returns an Actix [Route](actix_web::Route) that listens for a `GET` request and tries
|
||||
|
@ -491,11 +505,12 @@ pub fn render_app_to_stream_with_context<IV>(
|
|||
options: LeptosOptions,
|
||||
additional_context: impl Fn(leptos::Scope) + 'static + Clone + Send,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + 'static,
|
||||
method: Method,
|
||||
) -> Route
|
||||
where
|
||||
IV: IntoView,
|
||||
{
|
||||
web::get().to(move |req: HttpRequest| {
|
||||
let handler = move |req: HttpRequest| {
|
||||
let options = options.clone();
|
||||
let app_fn = app_fn.clone();
|
||||
let additional_context = additional_context.clone();
|
||||
|
@ -513,7 +528,14 @@ where
|
|||
|
||||
stream_app(&options, app, res_options, additional_context).await
|
||||
}
|
||||
})
|
||||
};
|
||||
match method {
|
||||
Method::Get => web::get().to(handler),
|
||||
Method::Post => web::post().to(handler),
|
||||
Method::Put => web::put().to(handler),
|
||||
Method::Delete => web::delete().to(handler),
|
||||
Method::Patch => web::patch().to(handler),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an Actix [Route](actix_web::Route) that listens for a `GET` request and tries
|
||||
|
@ -532,11 +554,12 @@ pub fn render_app_to_stream_in_order_with_context<IV>(
|
|||
options: LeptosOptions,
|
||||
additional_context: impl Fn(leptos::Scope) + 'static + Clone + Send,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + 'static,
|
||||
method: Method,
|
||||
) -> Route
|
||||
where
|
||||
IV: IntoView,
|
||||
{
|
||||
web::get().to(move |req: HttpRequest| {
|
||||
let handler = move |req: HttpRequest| {
|
||||
let options = options.clone();
|
||||
let app_fn = app_fn.clone();
|
||||
let additional_context = additional_context.clone();
|
||||
|
@ -555,7 +578,14 @@ where
|
|||
stream_app_in_order(&options, app, res_options, additional_context)
|
||||
.await
|
||||
}
|
||||
})
|
||||
};
|
||||
match method {
|
||||
Method::Get => web::get().to(handler),
|
||||
Method::Post => web::post().to(handler),
|
||||
Method::Put => web::put().to(handler),
|
||||
Method::Delete => web::delete().to(handler),
|
||||
Method::Patch => web::patch().to(handler),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an Actix [Route](actix_web::Route) that listens for a `GET` request and tries
|
||||
|
@ -575,11 +605,12 @@ pub fn render_app_async_with_context<IV>(
|
|||
options: LeptosOptions,
|
||||
additional_context: impl Fn(leptos::Scope) + 'static + Clone + Send,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + 'static,
|
||||
method: Method,
|
||||
) -> Route
|
||||
where
|
||||
IV: IntoView,
|
||||
{
|
||||
web::get().to(move |req: HttpRequest| {
|
||||
let handler = move |req: HttpRequest| {
|
||||
let options = options.clone();
|
||||
let app_fn = app_fn.clone();
|
||||
let additional_context = additional_context.clone();
|
||||
|
@ -603,7 +634,14 @@ where
|
|||
)
|
||||
.await
|
||||
}
|
||||
})
|
||||
};
|
||||
match method {
|
||||
Method::Get => web::get().to(handler),
|
||||
Method::Post => web::post().to(handler),
|
||||
Method::Put => web::put().to(handler),
|
||||
Method::Delete => web::delete().to(handler),
|
||||
Method::Patch => web::patch().to(handler),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an Actix [Route](actix_web::Route) that listens for a `GET` request and tries
|
||||
|
@ -854,7 +892,7 @@ async fn render_app_async_helper(
|
|||
/// as an argument so it can walk you app tree. This version is tailored to generated Actix compatible paths.
|
||||
pub fn generate_route_list<IV>(
|
||||
app_fn: impl FnOnce(leptos::Scope) -> IV + 'static,
|
||||
) -> Vec<(String, SsrMode)>
|
||||
) -> Vec<RouteListing>
|
||||
where
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
|
@ -863,11 +901,16 @@ where
|
|||
// Empty strings screw with Actix pathing, they need to be "/"
|
||||
routes = routes
|
||||
.into_iter()
|
||||
.map(|(s, mode)| {
|
||||
if s.is_empty() {
|
||||
return ("/".to_string(), mode);
|
||||
.map(|listing| {
|
||||
let path = listing.path();
|
||||
if path.is_empty() {
|
||||
return RouteListing::new(
|
||||
"/".to_string(),
|
||||
listing.mode(),
|
||||
listing.methods(),
|
||||
);
|
||||
}
|
||||
(s, mode)
|
||||
RouteListing::new(listing.path(), listing.mode(), listing.methods())
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
@ -877,14 +920,19 @@ where
|
|||
// Match `:some_word` but only capture `some_word` in the groups to replace with `{some_word}`
|
||||
let capture_re = Regex::new(r":((?:[^.,/]+)+)[^/]?").unwrap();
|
||||
|
||||
let routes: Vec<(String, SsrMode)> = routes
|
||||
let routes = routes
|
||||
.into_iter()
|
||||
.map(|(s, m)| (wildcard_re.replace_all(&s, "{tail:.*}").to_string(), m))
|
||||
.map(|(s, m)| (capture_re.replace_all(&s, "{$1}").to_string(), m))
|
||||
.collect();
|
||||
.map(|listing| {
|
||||
let path = wildcard_re
|
||||
.replace_all(&listing.path(), "{tail:.*}")
|
||||
.to_string();
|
||||
let path = capture_re.replace_all(&path, "{$1}").to_string();
|
||||
RouteListing::new(path, listing.mode(), listing.methods())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if routes.is_empty() {
|
||||
vec![("/".to_string(), Default::default())]
|
||||
vec![RouteListing::new("/", Default::default(), [Method::Get])]
|
||||
} else {
|
||||
routes
|
||||
}
|
||||
|
@ -901,7 +949,7 @@ pub trait LeptosRoutes {
|
|||
fn leptos_routes<IV>(
|
||||
self,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<(String, SsrMode)>,
|
||||
paths: Vec<RouteListing>,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static,
|
||||
) -> Self
|
||||
where
|
||||
|
@ -926,7 +974,7 @@ pub trait LeptosRoutes {
|
|||
fn leptos_routes_with_context<IV>(
|
||||
self,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<(String, SsrMode)>,
|
||||
paths: Vec<RouteListing>,
|
||||
additional_context: impl Fn(leptos::Scope) + 'static + Clone + Send,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static,
|
||||
) -> Self
|
||||
|
@ -948,7 +996,7 @@ where
|
|||
fn leptos_routes<IV>(
|
||||
self,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<(String, SsrMode)>,
|
||||
paths: Vec<RouteListing>,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static,
|
||||
) -> Self
|
||||
where
|
||||
|
@ -988,7 +1036,7 @@ where
|
|||
fn leptos_routes_with_context<IV>(
|
||||
self,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<(String, SsrMode)>,
|
||||
paths: Vec<RouteListing>,
|
||||
additional_context: impl Fn(leptos::Scope) + 'static + Clone + Send,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static,
|
||||
) -> Self
|
||||
|
@ -996,29 +1044,39 @@ where
|
|||
IV: IntoView + 'static,
|
||||
{
|
||||
let mut router = self;
|
||||
for (path, mode) in paths.iter() {
|
||||
router = router.route(
|
||||
path,
|
||||
match mode {
|
||||
SsrMode::OutOfOrder => render_app_to_stream_with_context(
|
||||
options.clone(),
|
||||
additional_context.clone(),
|
||||
app_fn.clone(),
|
||||
),
|
||||
SsrMode::InOrder => {
|
||||
render_app_to_stream_in_order_with_context(
|
||||
for listing in paths.iter() {
|
||||
let path = listing.path();
|
||||
let mode = listing.mode();
|
||||
|
||||
for method in listing.methods() {
|
||||
router = router.route(
|
||||
path,
|
||||
match mode {
|
||||
SsrMode::OutOfOrder => {
|
||||
render_app_to_stream_with_context(
|
||||
options.clone(),
|
||||
additional_context.clone(),
|
||||
app_fn.clone(),
|
||||
method,
|
||||
)
|
||||
}
|
||||
SsrMode::InOrder => {
|
||||
render_app_to_stream_in_order_with_context(
|
||||
options.clone(),
|
||||
additional_context.clone(),
|
||||
app_fn.clone(),
|
||||
method,
|
||||
)
|
||||
}
|
||||
SsrMode::Async => render_app_async_with_context(
|
||||
options.clone(),
|
||||
additional_context.clone(),
|
||||
app_fn.clone(),
|
||||
)
|
||||
}
|
||||
SsrMode::Async => render_app_async_with_context(
|
||||
options.clone(),
|
||||
additional_context.clone(),
|
||||
app_fn.clone(),
|
||||
),
|
||||
},
|
||||
);
|
||||
method,
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
router
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ use axum::{
|
|||
HeaderMap, Request, StatusCode,
|
||||
},
|
||||
response::IntoResponse,
|
||||
routing::get,
|
||||
routing::{delete, get, patch, post, put},
|
||||
};
|
||||
use futures::{
|
||||
channel::mpsc::{Receiver, Sender},
|
||||
|
@ -995,12 +995,12 @@ where
|
|||
/// as an argument so it can walk you app tree. This version is tailored to generate Axum compatible paths.
|
||||
pub async fn generate_route_list<IV>(
|
||||
app_fn: impl FnOnce(Scope) -> IV + 'static,
|
||||
) -> Vec<(String, SsrMode)>
|
||||
) -> Vec<RouteListing>
|
||||
where
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct Routes(pub Arc<RwLock<Vec<(String, SsrMode)>>>);
|
||||
pub struct Routes(pub Arc<RwLock<Vec<RouteListing>>>);
|
||||
|
||||
let routes = Routes::default();
|
||||
let routes_inner = routes.clone();
|
||||
|
@ -1024,17 +1024,26 @@ where
|
|||
// Axum's Router defines Root routes as "/" not ""
|
||||
let routes = routes
|
||||
.into_iter()
|
||||
.map(|(s, m)| {
|
||||
if s.is_empty() {
|
||||
("/".to_string(), m)
|
||||
.map(|listing| {
|
||||
let path = listing.path();
|
||||
if path.is_empty() {
|
||||
RouteListing::new(
|
||||
"/",
|
||||
Default::default(),
|
||||
[leptos_router::Method::Get],
|
||||
)
|
||||
} else {
|
||||
(s, m)
|
||||
listing
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if routes.is_empty() {
|
||||
vec![("/".to_string(), Default::default())]
|
||||
vec![RouteListing::new(
|
||||
"/",
|
||||
Default::default(),
|
||||
[leptos_router::Method::Get],
|
||||
)]
|
||||
} else {
|
||||
routes
|
||||
}
|
||||
|
@ -1046,7 +1055,7 @@ pub trait LeptosRoutes {
|
|||
fn leptos_routes<IV>(
|
||||
self,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<(String, SsrMode)>,
|
||||
paths: Vec<RouteListing>,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static,
|
||||
) -> Self
|
||||
where
|
||||
|
@ -1055,7 +1064,7 @@ pub trait LeptosRoutes {
|
|||
fn leptos_routes_with_context<IV>(
|
||||
self,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<(String, SsrMode)>,
|
||||
paths: Vec<RouteListing>,
|
||||
additional_context: impl Fn(leptos::Scope) + 'static + Clone + Send,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static,
|
||||
) -> Self
|
||||
|
@ -1064,7 +1073,7 @@ pub trait LeptosRoutes {
|
|||
|
||||
fn leptos_routes_with_handler<H, T>(
|
||||
self,
|
||||
paths: Vec<(String, SsrMode)>,
|
||||
paths: Vec<RouteListing>,
|
||||
handler: H,
|
||||
) -> Self
|
||||
where
|
||||
|
@ -1077,7 +1086,7 @@ impl LeptosRoutes for axum::Router {
|
|||
fn leptos_routes<IV>(
|
||||
self,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<(String, SsrMode)>,
|
||||
paths: Vec<RouteListing>,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static,
|
||||
) -> Self
|
||||
where
|
||||
|
@ -1089,7 +1098,7 @@ impl LeptosRoutes for axum::Router {
|
|||
fn leptos_routes_with_context<IV>(
|
||||
self,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<(String, SsrMode)>,
|
||||
paths: Vec<RouteListing>,
|
||||
additional_context: impl Fn(leptos::Scope) + 'static + Clone + Send,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static,
|
||||
) -> Self
|
||||
|
@ -1097,38 +1106,65 @@ impl LeptosRoutes for axum::Router {
|
|||
IV: IntoView + 'static,
|
||||
{
|
||||
let mut router = self;
|
||||
for (path, mode) in paths.iter() {
|
||||
router = router.route(
|
||||
path,
|
||||
match mode {
|
||||
SsrMode::OutOfOrder => {
|
||||
get(render_app_to_stream_with_context(
|
||||
options.clone(),
|
||||
additional_context.clone(),
|
||||
app_fn.clone(),
|
||||
))
|
||||
}
|
||||
SsrMode::InOrder => {
|
||||
get(render_app_to_stream_in_order_with_context(
|
||||
options.clone(),
|
||||
additional_context.clone(),
|
||||
app_fn.clone(),
|
||||
))
|
||||
}
|
||||
SsrMode::Async => get(render_app_async_with_context(
|
||||
options.clone(),
|
||||
additional_context.clone(),
|
||||
app_fn.clone(),
|
||||
)),
|
||||
},
|
||||
);
|
||||
for listing in paths.iter() {
|
||||
let path = listing.path();
|
||||
|
||||
for method in listing.methods() {
|
||||
router = router.route(
|
||||
path,
|
||||
match listing.mode() {
|
||||
SsrMode::OutOfOrder => {
|
||||
let s = render_app_to_stream_with_context(
|
||||
options.clone(),
|
||||
additional_context.clone(),
|
||||
app_fn.clone(),
|
||||
);
|
||||
match method {
|
||||
leptos_router::Method::Get => get(s),
|
||||
leptos_router::Method::Post => post(s),
|
||||
leptos_router::Method::Put => put(s),
|
||||
leptos_router::Method::Delete => delete(s),
|
||||
leptos_router::Method::Patch => patch(s),
|
||||
}
|
||||
}
|
||||
SsrMode::InOrder => {
|
||||
let s = render_app_to_stream_in_order_with_context(
|
||||
options.clone(),
|
||||
additional_context.clone(),
|
||||
app_fn.clone(),
|
||||
);
|
||||
match method {
|
||||
leptos_router::Method::Get => get(s),
|
||||
leptos_router::Method::Post => post(s),
|
||||
leptos_router::Method::Put => put(s),
|
||||
leptos_router::Method::Delete => delete(s),
|
||||
leptos_router::Method::Patch => patch(s),
|
||||
}
|
||||
}
|
||||
SsrMode::Async => {
|
||||
let s = render_app_async_with_context(
|
||||
options.clone(),
|
||||
additional_context.clone(),
|
||||
app_fn.clone(),
|
||||
);
|
||||
match method {
|
||||
leptos_router::Method::Get => get(s),
|
||||
leptos_router::Method::Post => post(s),
|
||||
leptos_router::Method::Put => put(s),
|
||||
leptos_router::Method::Delete => delete(s),
|
||||
leptos_router::Method::Patch => patch(s),
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
router
|
||||
}
|
||||
|
||||
fn leptos_routes_with_handler<H, T>(
|
||||
self,
|
||||
paths: Vec<(String, SsrMode)>,
|
||||
paths: Vec<RouteListing>,
|
||||
handler: H,
|
||||
) -> Self
|
||||
where
|
||||
|
@ -1136,8 +1172,21 @@ impl LeptosRoutes for axum::Router {
|
|||
T: 'static,
|
||||
{
|
||||
let mut router = self;
|
||||
for (path, _) in paths.iter() {
|
||||
router = router.route(path, get(handler.clone()));
|
||||
for listing in paths.iter() {
|
||||
for method in listing.methods() {
|
||||
router = router.route(
|
||||
listing.path(),
|
||||
match method {
|
||||
leptos_router::Method::Get => get(handler.clone()),
|
||||
leptos_router::Method::Post => post(handler.clone()),
|
||||
leptos_router::Method::Put => put(handler.clone()),
|
||||
leptos_router::Method::Delete => {
|
||||
delete(handler.clone())
|
||||
}
|
||||
leptos_router::Method::Patch => patch(handler.clone()),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
router
|
||||
}
|
||||
|
|
|
@ -944,12 +944,12 @@ where
|
|||
/// as an argument so it can walk you app tree. This version is tailored to generate Viz compatible paths.
|
||||
pub async fn generate_route_list<IV>(
|
||||
app_fn: impl FnOnce(Scope) -> IV + 'static,
|
||||
) -> Vec<(String, SsrMode)>
|
||||
) -> Vec<RouteListing>
|
||||
where
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct Routes(pub Arc<RwLock<Vec<(String, SsrMode)>>>);
|
||||
pub struct Routes(pub Arc<RwLock<Vec<RouteListing>>>);
|
||||
|
||||
let routes = Routes::default();
|
||||
let routes_inner = routes.clone();
|
||||
|
@ -973,17 +973,26 @@ where
|
|||
// Viz's Router defines Root routes as "/" not ""
|
||||
let routes = routes
|
||||
.into_iter()
|
||||
.map(|(s, m)| {
|
||||
if s.is_empty() {
|
||||
("/".to_string(), m)
|
||||
.map(|listing| {
|
||||
let path = listing.path();
|
||||
if path.is_empty() {
|
||||
RouteListing::new(
|
||||
"/",
|
||||
Default::default(),
|
||||
[leptos_router::Method::Get],
|
||||
)
|
||||
} else {
|
||||
(s, m)
|
||||
listing
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if routes.is_empty() {
|
||||
vec![("/".to_string(), Default::default())]
|
||||
vec![RouteListing::new(
|
||||
"/",
|
||||
Default::default(),
|
||||
[leptos_router::Method::Get],
|
||||
)]
|
||||
} else {
|
||||
routes
|
||||
}
|
||||
|
@ -995,7 +1004,7 @@ pub trait LeptosRoutes {
|
|||
fn leptos_routes<IV>(
|
||||
self,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<(String, SsrMode)>,
|
||||
paths: Vec<RouteListing>,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + Sync + 'static,
|
||||
) -> Self
|
||||
where
|
||||
|
@ -1004,7 +1013,7 @@ pub trait LeptosRoutes {
|
|||
fn leptos_routes_with_context<IV>(
|
||||
self,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<(String, SsrMode)>,
|
||||
paths: Vec<RouteListing>,
|
||||
additional_context: impl Fn(leptos::Scope) + Clone + Send + Sync + 'static,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + Sync + 'static,
|
||||
) -> Self
|
||||
|
@ -1013,7 +1022,7 @@ pub trait LeptosRoutes {
|
|||
|
||||
fn leptos_routes_with_handler<H, O>(
|
||||
self,
|
||||
paths: Vec<(String, SsrMode)>,
|
||||
paths: Vec<RouteListing>,
|
||||
handler: H,
|
||||
) -> Self
|
||||
where
|
||||
|
@ -1026,7 +1035,7 @@ impl LeptosRoutes for Router {
|
|||
fn leptos_routes<IV>(
|
||||
self,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<(String, SsrMode)>,
|
||||
paths: Vec<RouteListing>,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + Sync + 'static,
|
||||
) -> Self
|
||||
where
|
||||
|
@ -1038,52 +1047,93 @@ impl LeptosRoutes for Router {
|
|||
fn leptos_routes_with_context<IV>(
|
||||
self,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<(String, SsrMode)>,
|
||||
paths: Vec<RouteListing>,
|
||||
additional_context: impl Fn(leptos::Scope) + Clone + Send + Sync + 'static,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + Sync + 'static,
|
||||
) -> Self
|
||||
where
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
paths.iter().fold(self, |router, (path, mode)| match mode {
|
||||
SsrMode::OutOfOrder => router.get(
|
||||
path,
|
||||
render_app_to_stream_with_context(
|
||||
options.clone(),
|
||||
additional_context.clone(),
|
||||
app_fn.clone(),
|
||||
),
|
||||
),
|
||||
SsrMode::InOrder => router.get(
|
||||
path,
|
||||
render_app_to_stream_in_order_with_context(
|
||||
options.clone(),
|
||||
additional_context.clone(),
|
||||
app_fn.clone(),
|
||||
),
|
||||
),
|
||||
SsrMode::Async => router.get(
|
||||
path,
|
||||
render_app_async_with_context(
|
||||
options.clone(),
|
||||
additional_context.clone(),
|
||||
app_fn.clone(),
|
||||
),
|
||||
),
|
||||
paths.iter().fold(self, |router, listing| {
|
||||
let path = listing.path();
|
||||
let mode = listing.mode();
|
||||
|
||||
listing.methods().fold(router, |router, method| match mode {
|
||||
SsrMode::OutOfOrder => {
|
||||
let s = render_app_to_stream_with_context(
|
||||
options.clone(),
|
||||
additional_context.clone(),
|
||||
app_fn.clone(),
|
||||
);
|
||||
match method {
|
||||
leptos_router::Method::Get => router.get(path, s),
|
||||
leptos_router::Method::Post => router.post(path, s),
|
||||
leptos_router::Method::Put => router.put(path, s),
|
||||
leptos_router::Method::Delete => router.delete(path, s),
|
||||
leptos_router::Method::Patch => router.patch(path, s),
|
||||
}
|
||||
}
|
||||
SsrMode::InOrder => {
|
||||
let s = render_app_to_stream_in_order_with_context(
|
||||
options.clone(),
|
||||
additional_context.clone(),
|
||||
app_fn.clone(),
|
||||
);
|
||||
match method {
|
||||
leptos_router::Method::Get => router.get(path, s),
|
||||
leptos_router::Method::Post => router.post(path, s),
|
||||
leptos_router::Method::Put => router.put(path, s),
|
||||
leptos_router::Method::Delete => router.delete(path, s),
|
||||
leptos_router::Method::Patch => router.patch(path, s),
|
||||
}
|
||||
}
|
||||
SsrMode::Async => {
|
||||
let s = render_app_async_with_context(
|
||||
options.clone(),
|
||||
additional_context.clone(),
|
||||
app_fn.clone(),
|
||||
);
|
||||
match method {
|
||||
leptos_router::Method::Get => router.get(path, s),
|
||||
leptos_router::Method::Post => router.post(path, s),
|
||||
leptos_router::Method::Put => router.put(path, s),
|
||||
leptos_router::Method::Delete => router.delete(path, s),
|
||||
leptos_router::Method::Patch => router.patch(path, s),
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn leptos_routes_with_handler<H, O>(
|
||||
self,
|
||||
paths: Vec<(String, SsrMode)>,
|
||||
paths: Vec<RouteListing>,
|
||||
handler: H,
|
||||
) -> Self
|
||||
where
|
||||
H: Handler<Request, Output = Result<O>> + Clone,
|
||||
O: IntoResponse + Send + Sync + 'static,
|
||||
{
|
||||
paths
|
||||
.iter()
|
||||
.fold(self, |router, (path, _)| router.get(path, handler.clone()))
|
||||
paths.iter().fold(self, |router, listing| {
|
||||
listing
|
||||
.methods()
|
||||
.fold(router, |router, method| match method {
|
||||
leptos_router::Method::Get => {
|
||||
router.get(listing.path(), handler.clone())
|
||||
}
|
||||
leptos_router::Method::Post => {
|
||||
router.post(listing.path(), handler.clone())
|
||||
}
|
||||
leptos_router::Method::Put => {
|
||||
router.put(listing.path(), handler.clone())
|
||||
}
|
||||
leptos_router::Method::Delete => {
|
||||
router.delete(listing.path(), handler.clone())
|
||||
}
|
||||
leptos_router::Method::Patch => {
|
||||
router.patch(listing.path(), handler.clone())
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,33 @@ thread_local! {
|
|||
static ROUTE_ID: Cell<usize> = Cell::new(0);
|
||||
}
|
||||
|
||||
/// Represents an HTTP method that can be handled by this route.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Method {
|
||||
/// The [`GET`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET) method
|
||||
/// requests a representation of the specified resource.
|
||||
Get,
|
||||
/// The [`POST`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST) method
|
||||
/// submits an entity to the specified resource, often causing a change in
|
||||
/// state or side effects on the server.
|
||||
Post,
|
||||
/// The [`PUT`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT) method
|
||||
/// replaces all current representations of the target resource with the request payload.
|
||||
Put,
|
||||
/// The [`DELETE`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE) method
|
||||
/// deletes the specified resource.
|
||||
Delete,
|
||||
/// The [`PATCH`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH) method
|
||||
/// applies partial modifications to a resource.
|
||||
Patch,
|
||||
}
|
||||
|
||||
impl Default for Method {
|
||||
fn default() -> Self {
|
||||
Method::Get
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes a portion of the nested layout of the app, specifying the route it should match,
|
||||
/// the element it should display, and data that should be loaded alongside the route.
|
||||
#[component(transparent)]
|
||||
|
@ -25,6 +52,7 @@ pub fn Route<E, F, P>(
|
|||
/// The mode that this route prefers during server-side rendering. Defaults to out-of-order streaming.
|
||||
#[prop(optional)]
|
||||
ssr: SsrMode,
|
||||
#[prop(default = &[Method::Get])] methods: &'static [Method],
|
||||
/// `children` may be empty or include nested routes.
|
||||
#[prop(optional)]
|
||||
children: Option<Children>,
|
||||
|
@ -40,6 +68,7 @@ where
|
|||
path.to_string(),
|
||||
Rc::new(move |cx| view(cx).into_view(cx)),
|
||||
ssr,
|
||||
methods,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -62,6 +91,7 @@ pub fn ProtectedRoute<P, E, F, C>(
|
|||
/// The mode that this route prefers during server-side rendering. Defaults to out-of-order streaming.
|
||||
#[prop(optional)]
|
||||
ssr: SsrMode,
|
||||
#[prop(default = &[Method::Get])] methods: &'static [Method],
|
||||
/// `children` may be empty or include nested routes.
|
||||
#[prop(optional)]
|
||||
children: Option<Children>,
|
||||
|
@ -88,6 +118,7 @@ where
|
|||
}
|
||||
}),
|
||||
ssr,
|
||||
methods,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -97,6 +128,7 @@ pub(crate) fn define_route(
|
|||
path: String,
|
||||
view: Rc<dyn Fn(Scope) -> View>,
|
||||
ssr_mode: SsrMode,
|
||||
methods: &'static [Method],
|
||||
) -> RouteDefinition {
|
||||
let children = children
|
||||
.map(|children| {
|
||||
|
@ -125,6 +157,7 @@ pub(crate) fn define_route(
|
|||
children,
|
||||
view,
|
||||
ssr_mode,
|
||||
methods,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +1,57 @@
|
|||
use crate::{Branch, RouterIntegrationContext, ServerIntegration, SsrMode};
|
||||
use crate::{
|
||||
Branch, Method, RouterIntegrationContext, ServerIntegration, SsrMode,
|
||||
};
|
||||
use leptos::*;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use std::{cell::RefCell, collections::HashSet, rc::Rc};
|
||||
|
||||
/// Context to contain all possible routes.
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct PossibleBranchContext(pub(crate) Rc<RefCell<Vec<Branch>>>);
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
/// A route that this application can serve.
|
||||
pub struct RouteListing {
|
||||
path: String,
|
||||
mode: SsrMode,
|
||||
methods: HashSet<Method>,
|
||||
}
|
||||
|
||||
impl RouteListing {
|
||||
/// Create a route listing from its parts.
|
||||
pub fn new(
|
||||
path: impl ToString,
|
||||
mode: SsrMode,
|
||||
methods: impl IntoIterator<Item = Method>,
|
||||
) -> Self {
|
||||
Self {
|
||||
path: path.to_string(),
|
||||
mode,
|
||||
methods: methods.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The path this route handles.
|
||||
pub fn path(&self) -> &str {
|
||||
&self.path
|
||||
}
|
||||
|
||||
/// The rendering mode for this path.
|
||||
pub fn mode(&self) -> SsrMode {
|
||||
self.mode
|
||||
}
|
||||
|
||||
/// The HTTP request methods this path can handle.
|
||||
pub fn methods(&self) -> impl Iterator<Item = Method> + '_ {
|
||||
self.methods.iter().copied()
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a list of all routes this application could possibly serve. This returns the raw routes in the leptos_router
|
||||
/// format. Odds are you want `generate_route_list()` from either the actix, axum, or viz integrations if you want
|
||||
/// to work with their router
|
||||
pub fn generate_route_list_inner<IV>(
|
||||
app_fn: impl FnOnce(Scope) -> IV + 'static,
|
||||
) -> Vec<(String, SsrMode)>
|
||||
) -> Vec<RouteListing>
|
||||
where
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
|
@ -39,9 +79,19 @@ where
|
|||
.map(|route| route.key.ssr_mode)
|
||||
.max()
|
||||
.unwrap_or_default();
|
||||
let methods = branch
|
||||
.routes
|
||||
.iter()
|
||||
.flat_map(|route| route.key.methods)
|
||||
.copied()
|
||||
.collect::<HashSet<_>>();
|
||||
let pattern =
|
||||
branch.routes.last().map(|route| route.pattern.clone());
|
||||
pattern.map(|pattern| (pattern, mode))
|
||||
pattern.map(|path| RouteListing {
|
||||
path,
|
||||
mode,
|
||||
methods: methods.clone(),
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::SsrMode;
|
||||
use crate::{Method, SsrMode};
|
||||
use leptos::{leptos_dom::View, *};
|
||||
use std::rc::Rc;
|
||||
|
||||
|
@ -17,6 +17,8 @@ pub struct RouteDefinition {
|
|||
pub view: Rc<dyn Fn(Scope) -> View>,
|
||||
/// The mode this route prefers during server-side rendering.
|
||||
pub ssr_mode: SsrMode,
|
||||
/// The HTTP request methods this route is able to handle.
|
||||
pub methods: &'static [Method],
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for RouteDefinition {
|
||||
|
|
Loading…
Reference in a new issue