leptos-rs / leptos

Build fast web applications with Rust.
https://leptos.dev
MIT License
15.28k stars 599 forks source link

Setting trailing_slash=Strict does not work with nested Routes #2624

Open metatoaster opened 3 weeks ago

metatoaster commented 3 weeks ago

Describe the bug

It appears the trailing slash improvements provided by #2154 and #2217 still is not quite working as one might expect.

Leptos Dependencies

I've created a fork and built the reproducible code as an example called router_trailing_slash_axum, so the dependency is based directly on the current main branch (or more directly, its Cargo.toml).

To Reproduce

My goal is to have the example app provide an /item/ route that would provide a listing of all items, and individual items can be accessed using a route like /item/1/. I had tried the following definition to take advantage of the nested routes as per documentation.

#[component(transparent)]
pub fn ItemRoutes() -> impl IntoView {
    view! {
        <Route path="/item" view=ItemTop>
            <Route path="/:id/" view=ItemView trailing_slash=TrailingSlash::Exact/>
            <Route path="/" view=ItemListing trailing_slash=TrailingSlash::Exact/>
        </Route>
    }
}

What ended up happening was the route was generated as /item, without the trailing slash, made apparent by the dbg! output generated by the example:

[src/main.rs:13:5] &routes = [
    RouteListing {
        path: "/item/:id/",
        leptos_path: "/item/:id/",
        ...
    },
    RouteListing {
        path: "/item",
        leptos_path: "/item",
        ...
    },
    ...
]

As opposed to path: "/item/", and that only the http://localhost:3000/item route was usable, instead of http://localhost:3000/item/.

I've also tried <Route path="/item/" view=ItemTop trailing_slash=TrailingSlash::Exact> and have the inner route be <Route path="" ...> instead, and that actually made it worse as the /item route no longer functioned. There are a number of other combinations I've tried but none achieved what I really wanted.

Note that the A anchors do work as expected in this example.

Expected behavior

I would have expected the route /item/ be generated. Moreover, I wanted to use Redirect instead, but...

Additional context

This might be related to the axum integration, if Exact is replaced with with Redirect, duplicate routes will be generated for axum. While this is the desired behavior I do plan to have other layers be provided to axum which might be able to mitigate the redirect manually, but this failure to deal with exact trailing slashes is a complete showstopper for what I want to do (as the real application I am building has these specific routes I need working, for example, /path/target.zip should be able to download the zip file, /path/target.zip/ should hit the application to generate a listing of the archive, /path/target.zip/dir/ might list a directory of the archive (or automatically expand the tree at that location), /path/target.zip/dir/file might allow the download of that individual file within the archive.

metatoaster commented 3 weeks ago

I will note that I am now aware of the planned changes in leptos 0.7 will involve refactoring how paths are done. I am wondering if there will be ways to fine-tune control over StaticSegment and ParamSegment for the specific use-case I am after I provided under "Additional context" in my previous post.

metatoaster commented 3 weeks ago

Actually, there is another problem (maybe again with axum) - I am following through this example, following the example with fallback:

<Routes>
  <Route path="/users" view=Users>
    <Route path=":id" view=UserProfile/>
    <Route path="" view=NoUser/>
  </Route>
</Routes>

It seems the NoUser equivalent is never matched in any case, the UserProfile component seems to be receiving :id as a Some("") and the fallback is never used, when I was expecting it to work it never did.

metatoaster commented 2 weeks ago

Not directly related, but still kind of related as this is still part of the leptos_router package and the consequence of needing trailing slashes causing issues extends to A tags.

The workaround for now that I've used is something like:

<Routes>
  <Route path="/items/" view=ItemListing/>
  <Route path="/items/:id/" view=ItemView/>
</Routes>

I use A tags with active_class something like this inside the <Router>:

<A href="/" active_class="active">"Home"</A>
<A href="/items/" exact=false active_class="active">"Items"</A>

The generated anchor will have the /items/ one have the active_class correctly assigned to class when the location is at /items/, but going to something like /items/123/, the active_class is not set. This most certainly is a separate issue but I am documenting this here for the mean time.