ibraheemdev / matchit

A high performance, zero-copy URL router.
https://docs.rs/matchit
MIT License
366 stars 37 forks source link

Implementing Support for suffixes eg. file extensions #17

Open apps4uco opened 2 years ago

apps4uco commented 2 years ago

Hi it would be really useful for my use case to support suffixes. e.g. routes like "/users/:id.png"

or even "/users/:id.:ext"

... or even "/users/*.png"

Although it is possible to do at the application level it quickly gets very complicated when multiple extensions are present, and it looks like it might be easier to implement it in Matchit.

According to what I understand from the docs the parameter extends up to the next slash, and so currently is not possible.

How easy would it be to change parameter names to not allow '.' and add something like ParamWithSuffix(String) to the enum NodeType.

I optimistically forked the repo and was planning to implement it but then I realized that I dont understand the code well enough... My fork just has the test case (that currently fails) See below:

use matchit::{InsertError, MatchError, Router};

fn router()->Result<Router<&'static str>,InsertError> {
    let mut router = Router::new();
    router.insert("/home", "Welcome!")?;
    router.insert("/users/:id", "A User")?;
    router.insert("/users/:id.png", "A User Photo Currently not working")?;
    router.insert("/users/:id/x.png", "Another User Photo that works")?;
    Ok(router)
}

#[test]
fn match_img()->Result<(),MatchError>{

    let router=router().unwrap();
    let matched = router.at("/users/978")?;
    assert_eq!(matched.params.get("id"), Some("978"));
    assert_eq!(*matched.value, "A User");
    Ok(())
}

To consider: how would multiple '.' be handled e.g. schema.foo.yaml

ibraheemdev commented 2 years ago

I would be open to adding this feature but it's not really a high priority for me. It might be easier to put some time into writing a param parser at the application level. As a side note, this is the main benefit of the {x} syntax for route parameters...

ibraheemdev commented 8 months ago

Now that 0.8 is released with the new syntax, I'll try to implement this. The tricky part is that we have to repeatedly attempt to match the suffix in the case that we don't get a full match earlier in the path, but the suffix exists elsewhere. This also means suffixes might not be as performant as a regular static segment, but hopefully this can be added without affect performance for existing routes.

avitex commented 2 months ago

Firstly, thank you @ibraheemdev for your work on 0.8.

If implementing this does not affect existing routes, I believe it's perfectly reasonable to expect that suffixes may not perform as efficiently as regular static segments. I care about performance, however the ability to differentiate routes by suffixes would greatly simplify my life as an upstream user of Axum. This is because the alternative involves forgoing the power of axum::Router/matchit and resorting to matching multiple suffixes in a less declarative manner within a single handler.

I'm looking forward to an implementation that resolves this issue and please let me know if I can help.

AMDmi3 commented 4 weeks ago

Just my 2¢ to support this feature and explain why it can't be handled in the application as you've suggested before: matchit is used in axum, the (most?) popular web framework in rust, and though axum offers great facilities for customization through middleware, these cannot be used to affect Router behavior (documentation is not completely clear here and can be interpreted as "url rewriting cannot affect routing" which would be understandable, but in practice it's also "url rewriting cannot affect captures", so I cannot write a middleware which strips file extensions from captures). Doing rewriting before routing, as the docs suggest, is not a viable solution here as you basically need to reimplement pattern matching for all routes to determine which URIs need rewriting.

So for all axum users "handling in application" would mean either reimplementing the routing or forking axum if you want generic solution. I don't consider either of these viable, so I have to stick with stripping suffixes from captures manually in each affected handler, and having suffix handling in machit would same me from duplicating this code in each handler.

For the record, my case only needs suffix stripping, that is if the capture doesn't have expected suffix, I'm happy with 404 and do not need checking alternative routes. I'm sure this logic can be implemented without any performance penalty, but I'm not sure that this specific case justifies syntax complication that will be required.

ibraheemdev commented 4 weeks ago

I spent some time working on this recently and have a branch that implements support for suffixes at the end of a route.