Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update matchit to 0.8.6 #3140

Open
ibraheemdev opened this issue Jan 2, 2025 · 9 comments · May be fixed by #3143
Open

Update matchit to 0.8.6 #3140

ibraheemdev opened this issue Jan 2, 2025 · 9 comments · May be fixed by #3143

Comments

@ibraheemdev
Copy link
Contributor

The latest release adds support for parameter suffixes. matchit is currently pinned to 0.8.5 because there was some concern about this being a breaking change but I am not sure about the details. Let me know if there is anything I can do to help with that.

@mladedav
Copy link
Collaborator

mladedav commented Jan 3, 2025

Thanks for the update. I'll try to take a look at the upgrade here.

@mladedav
Copy link
Collaborator

mladedav commented Jan 3, 2025

Just to make sure about precedence, matchit tries to find a match with the longest static prefix, and in case of tie longest static suffix, right?

So for example from these paths:

/{}ello-world
/hello{}world
/hello-{}

/hello-world would be matched by the last, right?


This change will lead to a subtle difference between nest and nest-service.

For example the following test behaves differently with the two methods:

#[crate::test]
async fn nest_at_prefix_capture() {
    let empty_routes = Router::new();
    let api_routes = Router::new().route(
        "/{b}",
        get(|Path((a, b)): Path<(String, String)>| async move { format!("a={a} b={b}") }),
    );

    let app = Router::new()
        .nest("/x{a}x", api_routes)
        .nest("/xax", empty_routes);

    let client = TestClient::new(app);

    let res = client.get("/xax/bar").await;
    assert_eq!(res.status(), StatusCode::OK);
    assert_eq!(res.text().await, "a=a b=bar");
}

if we use nest, everything works because there is only one Router with these routes:

/xax
/x{a}x/{b}
/{*__404_fallback}

so the second one is chosen.

In case of nest_service, we end up with

/xax/{*fallback}
/x{a}x/{*fallback}

so the first one is used, inner router of the nested service is called and a default 404 fallback is invoked.

@ibraheemdev
Copy link
Contributor Author

ibraheemdev commented Jan 3, 2025

So for example from these paths:

/{}ello-world
/hello{}world
/hello-{}

/hello-world would be matched by the last, right?

Yes, that is correct, though I didn't fully consider the implications of mixed suffixes and prefixes. Do you think these routes (or even any two of those routes) should be considered conflicting instead?

@ibraheemdev
Copy link
Contributor Author

This change will lead to a subtle difference between nest and nest-service.

For example the following test behaves differently with the two methods:

I don't fully understand why this problem is unique to suffixes. Wouldn't the same thing occur if the routes were /{xax}/ and /xax?

@mladedav
Copy link
Collaborator

mladedav commented Jan 3, 2025

Do you think these routes (or even any two of those routes) should be considered conflicting instead?

I think this is fine and we can just document the behavior and maybe discourage mixing of prefixes and suffixes in the same node. I can't really think right now about a scenario where I'd use a static prefix, let alone mix prefixes and suffixes.

I don't think they need to be considered conflicting, I just wanted to make sure that's how it works.

Wouldn't the same thing occur if the routes were /{xax}/ and /xax?

Right, I did not consider that. So this has always kind of been the case and the update doesn't really change anything in that regard.

@ibraheemdev
Copy link
Contributor Author

ibraheemdev commented Jan 3, 2025

I think this is fine and we can just document the behavior and maybe discourage mixing of prefixes and suffixes in the same node. I can't really think right now about a scenario where I'd use a static prefix, let alone mix prefixes and suffixes.
I don't think they need to be considered conflicting, I just wanted to make sure that's how it works.

I would prefer to disallow ambiguous routes, that has been my philosophy with most of matchit's requested features so I think it makes sense to be consistent. The current rules are mostly an implementation detail and haven't been thought out or documented. The route /{a}.{b} is similarly ambiguous against /a.b.c without making a decision about lazy or greedy matches, which is why it's currently unsupported.

I think there are three cases to consider:

  1. /prefix{wild} and {wild}suffix

This should be considered conflicting. The only way out is to prioritize either prefixes or suffixes, or to prioritize the longer/shorter prefix/suffix and conflict or fallback in the case that they are equal, neither of which seems more intuitive than the other.

  1. /prefix{wild} and prefix{wild}suffix
  2. /{wild}suffix and prefix{wild}suffix

This case is a little more nuanced and only ambiguous if the prefixes or suffixes match. It seems reasonable to prefer the second route in both cases, but this may be difficult to implement. It also becomes more unintuitive with mixed length prefixes/suffixes, e.g.

  • /prefixsuffi{x} and prefix{s}uffix
  • /{p}refixsuffix and prefix{s}uffix

So I think these should also be considered conflicting. Disallowing ambiguous routes within a segment should make the routing rules intuitive and easy to document overall.

I can implement these rules and release 0.9.0, which you may want to wait for as it would be a breaking change. Does that seem reasonable?

@mladedav
Copy link
Collaborator

mladedav commented Jan 3, 2025

If you want to change it, then there's no rush and we can wait for the updated version to prevent breakage.

Personally, I'd consider completely omitting having both a prefix and a suffix since I'm still not really convinced that it just doesn't just add complexity, both to resolution and mental overhead. Then you can declare any prefix to be conflicting with any suffix and then it's a simple case where the longest prefix/suffix matches.

If /prefix{wild} and /prefix{wild}suffix are conflicting, then it seems to me that mixing a path containing both a prefix and a suffix is impossible with any prefix or suffix that would match the same path. Or would you allow \pre{x} with \prefix{x}suffix?

And also by having /prefix{wild} and /prefix{wild}suffix, I assume that specializing both prefix and suffix, e.g. having both \p{x}x and \prefix{x}suffix would also be conflicting since the first one seems like a special case of specialization like that. Then if there is any route having both prefix and suffix, there can be no other overlapping route with captures except maybe for \{x}, right?

@ibraheemdev
Copy link
Contributor Author

ibraheemdev commented Jan 4, 2025

Or would you allow \pre{x} with \prefix{x}suffix?

Hm yeah this also might be problematic. You might be right that a given list of overlapping route segments can include, in order of priority,

  • Any number of static segments (/a, /b, ..).
  • One of the following:
    • Any number of parameters with suffixes (/{x}a, /{x}b, ..), prioritizing the longest suffix.
    • Any number of parameters with prefixes (/a{x}, /b{x}, ..), prioritizing the longest prefix.
    • A single parameter with both a prefix and a suffix (/a{x}b).
  • A single parameter without a suffix or prefix (/{x}).

@mladedav
Copy link
Collaborator

mladedav commented Jan 4, 2025

Yeah, I think that's good.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants