DioxusLabs/dioxus · error · syn::Error

wildcard path param must be the last path param

Error message

wildcard path param must be the last path param

What it means

A wildcard path segment written as `*name` (PathParam::WildCard) only matches 'the rest of the path', so it is only legal as the final segment. RouteParser::new iterates segments and raises this error for any WildCard at index != last (packages/fullstack-macro/src/lib.rs:999-1009).

Source

Thrown at packages/fullstack-macro/src/lib.rs:1004

            return Err(syn::Error::new(span, "expected path to start with '/'"));
        }
        let path = path.strip_prefix('/').unwrap();

        let mut path_params = Vec::new();

        for path_param in path.split('/') {
            path_params.push((
                Slash(span),
                PathParam::new(path_param, span, Box::new(parse_quote!(())))?,
            ));
        }

        let path_param_len = path_params.len();
        for (i, (_slash, path_param)) in path_params.iter().enumerate() {
            match path_param {
                PathParam::WildCard(_, _, _, _, _, _) => {
                    if i != path_param_len - 1 {
                        return Err(syn::Error::new(
                            span,
                            "wildcard path param must be the last path param",
                        ));
                    }
                }
                PathParam::Capture(_, _, _, _, _) => (),
                PathParam::Static(lit) => {
                    if lit.value() == "*" && i != path_param_len - 1 {
                        return Err(syn::Error::new(
                            span,
                            "wildcard path param must be the last path param",
                        ));
                    }
                }
            }
        }

        let mut query_params = Vec::new();

View on GitHub (pinned to 393d190a80)

Solutions

  1. Move the wildcard to the end: `/files/{path}/meta` won't work either — instead capture per-segment with `{path}` (single segment) or restructure to `/files/meta/*rest`.
  2. If you need 'one segment anywhere', use a `{name}` capture and parse it yourself.
  3. If you truly need prefix-style matching, register separate routes per prefix and keep each wildcard last.

Example fix

// before
#[route(GET, "/api/*version/users")]
async fn users(version: Vec<String>) { ... }

// after
#[route(GET, "/api/{version}/users")]
async fn users(version: String) { ... }
Defensive patterns

Strategy: validation

Validate before calling

fn validate_route(route: &str) -> Result<(), String> {
    let path = route.split('?').next().unwrap().trim_start_matches('/');
    let segs: Vec<&str> = path.split('/').collect();
    for (i, seg) in segs.iter().enumerate() {
        if seg.starts_with('*') && i != segs.len() - 1 {
            return Err(format!("wildcard must be last segment: {route}"));
        }
    }
    Ok(())
}

Prevention

When it happens

Trigger: `#[route(GET, "/files/*path/meta")]` — segments after the wildcard; nesting a wildcard mid-path hoping for glob behavior; converting an axum `{*path}/tail` route where axum itself would reject it too.

Common situations: Assuming `*` acts like a shell glob that can match one segment in the middle; refactoring `/a/*rest` to `/a/*rest/b` and expecting it to still compile; designing URLs like `/api/*version/users`.

Related errors


AI-assisted analysis of DioxusLabs/dioxus@393d190a80 (2026-08-16). Data as JSON: /api/errors/d708654a384b28fd. Report an issue: GitHub.