DioxusLabs/dioxus · error · syn::Error

expected at most one '?'

Error message

expected at most one '?'

What it means

RouteParser::new splits the route literal on `?` and requires at most two parts (path, query). More than one `?` means the string is malformed — the second `?` would end up inside the query section and produce nonsense, so it is rejected up front (packages/fullstack-macro/src/lib.rs:975-979).

Source

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

            url_without_queries
        );

        Some(full_url)
    }
}

struct RouteParser {
    path_params: Vec<(Slash, PathParam)>,
    query_params: Vec<QueryParam>,
}

impl RouteParser {
    fn new(lit: LitStr) -> syn::Result<Self> {
        let val = lit.value();
        let span = lit.span();
        let split_route = val.split('?').collect::<Vec<_>>();
        if split_route.len() > 2 {
            return Err(syn::Error::new(span, "expected at most one '?'"));
        }

        let path = split_route[0];
        if !path.starts_with('/') {
            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();

View on GitHub (pinned to 393d190a80)

Solutions

  1. Keep exactly one `?` between path and query: `/search?q&other`.
  2. If a query value may contain `?`, it must be percent-encoded by the client (`%3F`) — never place a raw `?` in the route literal.
  3. Delete the stray `?` produced by string concatenation and use `&` between query params.

Example fix

// before
#[route(GET, "/search?q=?other")]
async fn search(q: String, other: String) { ... }

// after
#[route(GET, "/search?q&other")]
async fn search(q: String, other: String) { ... }
Defensive patterns

Strategy: validation

Validate before calling

fn validate_route(route: &str) -> Result<(), String> {
    if route.split('?').count() > 2 {
        return Err(format!("expected at most one '?': {route}"));
    }
    Ok(())
}
// Unit-test every route constant/string before committing:
#[test]
fn routes_well_formed() { for r in ["/items/{id}", "/search?q"] { validate_route(r).unwrap(); } }

Prevention

When it happens

Trigger: `#[route(GET, "/search?q=?other")]`; a query value containing a literal `?` such as `?q=a?b`; accidentally concatenating two route strings that each had their own query, e.g. `"/list?sort" + "&?page"`.

Common situations: Building route strings dynamically at authoring time; copying a URL with an embedded `?` from a browser address bar; misunderstanding that `?` is a structural separator here, not data.

Related errors


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